diff --git a/.licenserc.yaml b/.licenserc.yaml index a9a6a982268..a124f474bf7 100644 --- a/.licenserc.yaml +++ b/.licenserc.yaml @@ -79,6 +79,7 @@ header: - 'ext/apm-seata-skywalking-plugin/config/agent.config' - 'server/src/main/resources/lua/redislocker/redislock.lua' - 'server/src/main/resources/banner.txt' + - '**/*.json' comment: on-failure diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md index c5d20a93085..d628027a322 100644 --- a/changes/en-us/2.x.md +++ b/changes/en-us/2.x.md @@ -120,6 +120,10 @@ Add changes here for all PR submitted to the 2.x branch. ### refactor: - [[#6269](https://github.com/apache/incubator-seata/pull/6269)] standardize Seata Exception - [[#6419](https://github.com/apache/incubator-seata/pull/6419)] optimize integration-tx-api compatible +- [[#6427](https://github.com/apache/incubator-seata/pull/6427)] support spi、saga、spring module compatible + +### refactor: +- [[#6269](https://github.com/apache/incubator-seata/pull/6269)] standardize Seata Exception - [[#6405](https://github.com/apache/incubator-seata/pull/6405)] fix kotlin compile failure - [[#6412](https://github.com/apache/incubator-seata/pull/6412)] optimize core compatible module - [[#6429](https://github.com/apache/incubator-seata/pull/6429)] remove repetitive words @@ -178,6 +182,7 @@ Thanks to these contributors for their code commits. Please report an unintended - [gggyd123](https://github.com/gggyd123) - [jonasHanhan](https://github.com/jonasHanhan) - [Code-breaker1998](https://github.com/Code-breaker1998) +- [yixia](https://github.com/wt-better) - [MikhailNavitski](https://github.com/MikhailNavitski) - [deung](https://github.com/deung) diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md index 416ab9cc470..4a22dcf99ea 100644 --- a/changes/zh-cn/2.x.md +++ b/changes/zh-cn/2.x.md @@ -116,6 +116,7 @@ - [[#6387](https://github.com/apache/incubator-seata/pull/6387)] 优化tcc使用兼容 - [[#6402](https://github.com/apache/incubator-seata/pull/6402)] 优化rm-datasource向下兼容 - [[#6419](https://github.com/apache/incubator-seata/pull/6419)] 优化integration-tx-api向下兼容 +- [[#6427](https://github.com/apache/incubator-seata/pull/6427)] 支持spi、saga、spring模块的向下兼容 - [[#6442](https://github.com/apache/incubator-seata/pull/6442)] 阐明 if @@ -177,6 +178,7 @@ - [gggyd123](https://github.com/gggyd123) - [jonasHanhan](https://github.com/jonasHanhan) - [Code-breaker1998](https://github.com/Code-breaker1998) +- [yixia](https://github.com/wt-better) - [MikhailNavitski](https://github.com/MikhailNavitski) - [deung](https://github.com/deung) diff --git a/common/src/main/java/org/apache/seata/common/loader/EnhancedServiceLoader.java b/common/src/main/java/org/apache/seata/common/loader/EnhancedServiceLoader.java index 9a18e45b44d..f2e0405f330 100644 --- a/common/src/main/java/org/apache/seata/common/loader/EnhancedServiceLoader.java +++ b/common/src/main/java/org/apache/seata/common/loader/EnhancedServiceLoader.java @@ -40,12 +40,9 @@ /** * The type Enhanced service loader. - * */ public class EnhancedServiceLoader { private static final Logger LOGGER = LoggerFactory.getLogger(EnhancedServiceLoader.class); - private static final String APACHE_SEATA_PACKAGE_NAME = "org.apache.seata"; - private static final String IO_SEATA_PACKAGE_NAME = "io.seata"; /** * Class->InnerEnhancedServiceLoader map @@ -53,16 +50,6 @@ public class EnhancedServiceLoader { private static final ConcurrentMap, InnerEnhancedServiceLoader> SERVICE_LOADERS = new ConcurrentHashMap<>(); - private static Class getCompatibleService(Class originService) { - String apacheSeataName = originService.getName(); - String ioSeataName = apacheSeataName.replace(APACHE_SEATA_PACKAGE_NAME, IO_SEATA_PACKAGE_NAME); - try { - Class clasz = Class.forName(ioSeataName); - return clasz; - } catch (ClassNotFoundException e) { - return null; - } - } /** * Specify classLoader to load the service provider * @@ -73,15 +60,6 @@ private static Class getCompatibleService(Class originService) { * @throws EnhancedServiceNotFoundException the enhanced service not found exception */ public static S load(Class service, ClassLoader loader) throws EnhancedServiceNotFoundException { - Class compatibleService = getCompatibleService(service); - if (compatibleService != null) { - try { - return InnerEnhancedServiceLoader.getServiceLoader(service).load(loader); - } catch (EnhancedServiceNotFoundException ignore) { - LOGGER.warn("Can't load SPI for :{} from org.apache.seata package,will try to load SPI from io.seata package", service.getName()); - } - return InnerEnhancedServiceLoader.getServiceLoader(compatibleService).load(loader); - } return InnerEnhancedServiceLoader.getServiceLoader(service).load(loader); } @@ -94,15 +72,6 @@ public static S load(Class service, ClassLoader loader) throws EnhancedSe * @throws EnhancedServiceNotFoundException the enhanced service not found exception */ public static S load(Class service) throws EnhancedServiceNotFoundException { - Class compatibleService = getCompatibleService(service); - if (compatibleService != null) { - try { - return InnerEnhancedServiceLoader.getServiceLoader(service).load(findClassLoader()); - } catch (EnhancedServiceNotFoundException ignore) { - LOGGER.warn("Can't load SPI for :{} from org.apache.seata package,will try to load SPI from io.seata package", service.getName()); - } - return InnerEnhancedServiceLoader.getServiceLoader(compatibleService).load(findClassLoader()); - } return InnerEnhancedServiceLoader.getServiceLoader(service).load(findClassLoader()); } @@ -116,15 +85,6 @@ public static S load(Class service) throws EnhancedServiceNotFoundExcepti * @throws EnhancedServiceNotFoundException the enhanced service not found exception */ public static S load(Class service, String activateName) throws EnhancedServiceNotFoundException { - Class compatibleService = getCompatibleService(service); - if (compatibleService != null) { - try { - return InnerEnhancedServiceLoader.getServiceLoader(service).load(activateName, findClassLoader()); - } catch (EnhancedServiceNotFoundException ignore) { - LOGGER.warn("Can't load SPI for :{} from org.apache.seata package,will try to load SPI from io.seata package", service.getName()); - } - return InnerEnhancedServiceLoader.getServiceLoader(compatibleService).load(activateName, findClassLoader()); - } return InnerEnhancedServiceLoader.getServiceLoader(service).load(activateName, findClassLoader()); } @@ -140,15 +100,6 @@ public static S load(Class service, String activateName) throws EnhancedS */ public static S load(Class service, String activateName, ClassLoader loader) throws EnhancedServiceNotFoundException { - Class compatibleService = getCompatibleService(service); - if (compatibleService != null) { - try { - return InnerEnhancedServiceLoader.getServiceLoader(service).load(activateName, loader); - } catch (EnhancedServiceNotFoundException ignore) { - LOGGER.warn("Can't load SPI for :{} from org.apache.seata package,will try to load SPI from io.seata package", service.getName()); - } - return InnerEnhancedServiceLoader.getServiceLoader(compatibleService).load(activateName, loader); - } return InnerEnhancedServiceLoader.getServiceLoader(service).load(activateName, loader); } @@ -164,15 +115,6 @@ public static S load(Class service, String activateName, ClassLoader load */ public static S load(Class service, String activateName, Object[] args) throws EnhancedServiceNotFoundException { - Class compatibleService = getCompatibleService(service); - if (compatibleService != null) { - try { - return InnerEnhancedServiceLoader.getServiceLoader(service).load(activateName, args, findClassLoader()); - } catch (EnhancedServiceNotFoundException ignore) { - LOGGER.warn("Can't load SPI for :{} from org.apache.seata package,will try to load SPI from io.seata package", service.getName()); - } - return InnerEnhancedServiceLoader.getServiceLoader(compatibleService).load(activateName, args, findClassLoader()); - } return InnerEnhancedServiceLoader.getServiceLoader(service).load(activateName, args, findClassLoader()); } @@ -189,15 +131,6 @@ public static S load(Class service, String activateName, Object[] args) */ public static S load(Class service, String activateName, Class[] argsType, Object[] args) throws EnhancedServiceNotFoundException { - Class compatibleService = getCompatibleService(service); - if (compatibleService != null) { - try { - return InnerEnhancedServiceLoader.getServiceLoader(service).load(activateName, argsType, args, findClassLoader()); - } catch (EnhancedServiceNotFoundException ignore) { - LOGGER.warn("Can't load SPI for :{} from org.apache.seata package,will try to load SPI from io.seata package", service.getName()); - } - return InnerEnhancedServiceLoader.getServiceLoader(compatibleService).load(activateName, argsType, args, findClassLoader()); - } return InnerEnhancedServiceLoader.getServiceLoader(service).load(activateName, argsType, args, findClassLoader()); } @@ -209,15 +142,6 @@ public static S load(Class service, String activateName, Class[] argsT * @return list list */ public static List loadAll(Class service) { - Class compatibleService = getCompatibleService(service); - if (compatibleService != null) { - try { - return InnerEnhancedServiceLoader.getServiceLoader(service).loadAll(findClassLoader()); - } catch (EnhancedServiceNotFoundException ignore) { - LOGGER.warn("Can't load SPI for :{} from org.apache.seata package,will try to load SPI from io.seata package", service.getName()); - } - return InnerEnhancedServiceLoader.getServiceLoader(compatibleService).loadAll(findClassLoader()); - } return InnerEnhancedServiceLoader.getServiceLoader(service).loadAll(findClassLoader()); } @@ -231,15 +155,6 @@ public static List loadAll(Class service) { * @return list list */ public static List loadAll(Class service, Class[] argsType, Object[] args) { - Class compatibleService = getCompatibleService(service); - if (compatibleService != null) { - try { - return InnerEnhancedServiceLoader.getServiceLoader(service).loadAll(argsType, args, findClassLoader()); - } catch (EnhancedServiceNotFoundException ignore) { - LOGGER.warn("Can't load SPI for :{} from org.apache.seata package,will try to load SPI from io.seata package", service.getName()); - } - return InnerEnhancedServiceLoader.getServiceLoader(compatibleService).loadAll(argsType, args, findClassLoader()); - } return InnerEnhancedServiceLoader.getServiceLoader(service).loadAll(argsType, args, findClassLoader()); } @@ -257,17 +172,7 @@ public static void unloadAll() { * @param service the service */ public static void unload(Class service) { - Class compatibleService = getCompatibleService(service); - if (compatibleService != null) { - try { - InnerEnhancedServiceLoader.removeServiceLoader(service); - } catch (Exception ignore) { - LOGGER.warn("Can't unload SPI for :{} from org.apache.seata package,will try to unload SPI from io.seata package", service.getName()); - InnerEnhancedServiceLoader.removeServiceLoader(compatibleService); - } - } else { - InnerEnhancedServiceLoader.removeServiceLoader(service); - } + InnerEnhancedServiceLoader.removeServiceLoader(service); } /** @@ -281,20 +186,9 @@ public static void unload(Class service, String activateName) { if (activateName == null) { throw new IllegalArgumentException("activateName is null"); } - Class compatibleService = getCompatibleService(service); - if (compatibleService != null) { - try { - InnerEnhancedServiceLoader serviceLoader = InnerEnhancedServiceLoader.getServiceLoader(service); - doUnload(serviceLoader, activateName); - } catch (Exception ignore) { - LOGGER.warn("Can't unload SPI for :{} from org.apache.seata package,will try to unload SPI from io.seata package", service.getName()); - InnerEnhancedServiceLoader serviceLoader = InnerEnhancedServiceLoader.getServiceLoader(compatibleService); - doUnload(serviceLoader, activateName); - } - } else { - InnerEnhancedServiceLoader serviceLoader = InnerEnhancedServiceLoader.getServiceLoader(service); - doUnload(serviceLoader, activateName); - } + + InnerEnhancedServiceLoader serviceLoader = InnerEnhancedServiceLoader.getServiceLoader(service); + doUnload(serviceLoader, activateName); } private static void doUnload(InnerEnhancedServiceLoader serviceLoader, String activateName) { @@ -342,6 +236,7 @@ static List> getAllExtensionClass(Class service) { static List> getAllExtensionClass(Class service, ClassLoader loader) { return InnerEnhancedServiceLoader.getServiceLoader(service).getAllExtensionClass(loader); } + /** * Cannot use TCCL, in the pandora container will cause the class in the plugin not to be loaded * @@ -357,6 +252,9 @@ private static class InnerEnhancedServiceLoader { private static final String SERVICES_DIRECTORY = "META-INF/services/"; private static final String SEATA_DIRECTORY = "META-INF/seata/"; + private static final String APACHE_SEATA_PACKAGE_NAME = "org.apache.seata"; + private static final String IO_SEATA_PACKAGE_NAME = "io.seata"; + private final Class type; private final Holder>> definitionsHolder = new Holder<>(); private final ConcurrentMap, Holder> definitionToInstanceMap = @@ -369,7 +267,6 @@ private InnerEnhancedServiceLoader(Class type) { } - /** * Get the ServiceLoader for the specified Class * @@ -391,7 +288,7 @@ private static InnerEnhancedServiceLoader removeServiceLoader(Class ty if (type == null) { throw new IllegalArgumentException("Enhanced Service type is null"); } - return (InnerEnhancedServiceLoader)SERVICE_LOADERS.remove(type); + return (InnerEnhancedServiceLoader) SERVICE_LOADERS.remove(type); } private static void removeAllServiceLoader() { @@ -588,8 +485,20 @@ private List> loadAllExtensionClass(ClassLoader loader) { private List> findAllExtensionDefinition(ClassLoader loader) { List> extensionDefinitions = new ArrayList<>(); try { - loadFile(SERVICES_DIRECTORY, loader, extensionDefinitions); - loadFile(SEATA_DIRECTORY, loader, extensionDefinitions); + loadFile(SERVICES_DIRECTORY, type, loader, extensionDefinitions); + loadFile(SEATA_DIRECTORY, type, loader, extensionDefinitions); + + @SuppressWarnings("rawtypes") Class compatibleService = getCompatibleService(type); + if (compatibleService != null) { + if (type.isAssignableFrom(compatibleService)) { + LOGGER.info("Load compatible class {}", compatibleService.getName()); + loadFile(SERVICES_DIRECTORY, compatibleService, loader, extensionDefinitions); + loadFile(SEATA_DIRECTORY, compatibleService, loader, extensionDefinitions); + } else { + LOGGER.info("Ignore load compatible class {}, because is not assignable from origin type {}", compatibleService.getName(), type.getName()); + } + } + } catch (IOException e) { throw new EnhancedServiceNotFoundException(e); } @@ -616,8 +525,17 @@ private List> findAllExtensionDefinition(ClassLoader load return extensionDefinitions; } + private static Class getCompatibleService(Class originType) { + String ioSeataType = originType.getName().replace(APACHE_SEATA_PACKAGE_NAME, IO_SEATA_PACKAGE_NAME); + try { + return Class.forName(ioSeataType); + } catch (ClassNotFoundException e) { + return null; + } + } + - private void loadFile(String dir, ClassLoader loader, List> extensions) + private void loadFile(String dir, Class type, ClassLoader loader, List> extensions) throws IOException { String fileName = dir + type.getName(); Enumeration urls; diff --git a/compatible/pom.xml b/compatible/pom.xml index fc9fcc0a106..bcfbbbd769e 100644 --- a/compatible/pom.xml +++ b/compatible/pom.xml @@ -162,6 +162,13 @@ ${mariadb.version} test + + com.h2database + h2 + 1.4.200 + test + + diff --git a/compatible/src/main/java/io/seata/rm/AbstractRMHandler.java b/compatible/src/main/java/io/seata/core/model/ResourceManager.java similarity index 74% rename from compatible/src/main/java/io/seata/rm/AbstractRMHandler.java rename to compatible/src/main/java/io/seata/core/model/ResourceManager.java index 9945ef6d933..7ae82e49cca 100644 --- a/compatible/src/main/java/io/seata/rm/AbstractRMHandler.java +++ b/compatible/src/main/java/io/seata/core/model/ResourceManager.java @@ -14,11 +14,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.seata.rm; +package io.seata.core.model; + + +import org.apache.seata.common.loader.EnhancedServiceLoader; /** - * The Abstract RM event handler - * + * @see EnhancedServiceLoader.InnerEnhancedServiceLoader#findAllExtensionDefinition(ClassLoader) */ -public abstract class AbstractRMHandler extends org.apache.seata.rm.AbstractRMHandler { +public interface ResourceManager extends org.apache.seata.core.model.ResourceManager { } diff --git a/compatible/src/main/java/io/seata/saga/engine/StateMachineConfig.java b/compatible/src/main/java/io/seata/saga/engine/StateMachineConfig.java index 377b59dca66..4fe5df97ab2 100644 --- a/compatible/src/main/java/io/seata/saga/engine/StateMachineConfig.java +++ b/compatible/src/main/java/io/seata/saga/engine/StateMachineConfig.java @@ -19,11 +19,11 @@ import io.seata.saga.engine.expression.ExpressionFactoryManager; import io.seata.saga.engine.repo.StateLogRepository; import io.seata.saga.engine.repo.StateMachineRepository; +import io.seata.saga.engine.store.StateLogStore; import org.apache.seata.saga.engine.expression.ExpressionResolver; import org.apache.seata.saga.engine.invoker.ServiceInvokerManager; import org.apache.seata.saga.engine.sequence.SeqGenerator; import org.apache.seata.saga.engine.store.StateLangStore; -import org.apache.seata.saga.engine.store.StateLogStore; import org.apache.seata.saga.engine.strategy.StatusDecisionStrategy; import org.apache.seata.saga.proctrl.eventing.impl.ProcessCtrlEventPublisher; diff --git a/compatible/src/main/java/io/seata/saga/engine/config/DbStateMachineConfig.java b/compatible/src/main/java/io/seata/saga/engine/config/DbStateMachineConfig.java index c08d5a53818..0238654c568 100644 --- a/compatible/src/main/java/io/seata/saga/engine/config/DbStateMachineConfig.java +++ b/compatible/src/main/java/io/seata/saga/engine/config/DbStateMachineConfig.java @@ -16,6 +16,207 @@ */ package io.seata.saga.engine.config; -public class DbStateMachineConfig extends org.apache.seata.saga.engine.config.DbStateMachineConfig { +import io.seata.saga.engine.impl.DefaultStateMachineConfig; +import io.seata.saga.engine.repo.StateLogRepository; +import io.seata.saga.engine.store.impl.StateLogStoreImpl; +import org.apache.seata.common.ConfigurationKeys; +import org.apache.seata.config.Configuration; +import org.apache.seata.config.ConfigurationFactory; +import org.apache.seata.saga.engine.serializer.impl.ParamsSerializer; +import org.apache.seata.saga.engine.store.db.DbAndReportTcStateLogStore; +import org.apache.seata.saga.engine.store.db.DbStateLangStore; +import org.apache.seata.saga.engine.tm.DefaultSagaTransactionalTemplate; +import org.apache.seata.saga.engine.tm.SagaTransactionalTemplate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.util.StringUtils; +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.SQLException; + +import static org.apache.seata.common.DefaultValues.DEFAULT_CLIENT_REPORT_SUCCESS_ENABLE; +import static org.apache.seata.common.DefaultValues.DEFAULT_CLIENT_SAGA_BRANCH_REGISTER_ENABLE; +import static org.apache.seata.common.DefaultValues.DEFAULT_CLIENT_SAGA_COMPENSATE_PERSIST_MODE_UPDATE; +import static org.apache.seata.common.DefaultValues.DEFAULT_CLIENT_SAGA_RETRY_PERSIST_MODE_UPDATE; +import static org.apache.seata.common.DefaultValues.DEFAULT_SAGA_JSON_PARSER; + +public class DbStateMachineConfig extends DefaultStateMachineConfig implements DisposableBean { + + private static final Logger LOGGER = LoggerFactory.getLogger(DbStateMachineConfig.class); + + private DataSource dataSource; + private String applicationId; + private String txServiceGroup; + private String accessKey; + private String secretKey; + private String tablePrefix = "seata_"; + private String dbType; + private boolean rmReportSuccessEnable = DEFAULT_CLIENT_REPORT_SUCCESS_ENABLE; + private boolean sagaBranchRegisterEnable = DEFAULT_CLIENT_SAGA_BRANCH_REGISTER_ENABLE; + + private SagaTransactionalTemplate sagaTransactionalTemplate; + + public DbStateMachineConfig() { + try { + Configuration configuration = ConfigurationFactory.getInstance(); + if (configuration != null) { + this.rmReportSuccessEnable = configuration.getBoolean(ConfigurationKeys.CLIENT_REPORT_SUCCESS_ENABLE, DEFAULT_CLIENT_REPORT_SUCCESS_ENABLE); + this.sagaBranchRegisterEnable = configuration.getBoolean(ConfigurationKeys.CLIENT_SAGA_BRANCH_REGISTER_ENABLE, DEFAULT_CLIENT_SAGA_BRANCH_REGISTER_ENABLE); + setSagaJsonParser(configuration.getConfig(ConfigurationKeys.CLIENT_SAGA_JSON_PARSER, DEFAULT_SAGA_JSON_PARSER)); + this.applicationId = configuration.getConfig(ConfigurationKeys.APPLICATION_ID); + this.txServiceGroup = configuration.getConfig(ConfigurationKeys.TX_SERVICE_GROUP); + this.accessKey = configuration.getConfig(ConfigurationKeys.ACCESS_KEY, null); + this.secretKey = configuration.getConfig(ConfigurationKeys.SECRET_KEY, null); + setSagaRetryPersistModeUpdate(configuration.getBoolean(ConfigurationKeys.CLIENT_SAGA_RETRY_PERSIST_MODE_UPDATE, + DEFAULT_CLIENT_SAGA_RETRY_PERSIST_MODE_UPDATE)); + setSagaCompensatePersistModeUpdate(configuration.getBoolean(ConfigurationKeys.CLIENT_SAGA_COMPENSATE_PERSIST_MODE_UPDATE, + DEFAULT_CLIENT_SAGA_COMPENSATE_PERSIST_MODE_UPDATE)); + } + } catch (Exception e) { + LOGGER.warn("Load SEATA configuration failed, use default configuration instead.", e); + } + } + + public static String getDbTypeFromDataSource(DataSource dataSource) throws SQLException { + try (Connection con = dataSource.getConnection()) { + DatabaseMetaData metaData = con.getMetaData(); + return metaData.getDatabaseProductName(); + } + } + + @Override + public void afterPropertiesSet() throws Exception { + if (dataSource == null) { + throw new IllegalArgumentException("datasource required not null!"); + } + + dbType = getDbTypeFromDataSource(dataSource); + if (getStateLogStore() == null) { + DbAndReportTcStateLogStore dbStateLogStore = new DbAndReportTcStateLogStore(); + dbStateLogStore.setDataSource(dataSource); + dbStateLogStore.setTablePrefix(tablePrefix); + dbStateLogStore.setDbType(dbType); + dbStateLogStore.setDefaultTenantId(getDefaultTenantId()); + dbStateLogStore.setSeqGenerator(getSeqGenerator()); + + if (StringUtils.hasLength(getSagaJsonParser())) { + ParamsSerializer paramsSerializer = new ParamsSerializer(); + paramsSerializer.setJsonParserName(getSagaJsonParser()); + dbStateLogStore.setParamsSerializer(paramsSerializer); + } + + if (sagaTransactionalTemplate == null) { + DefaultSagaTransactionalTemplate defaultSagaTransactionalTemplate = new DefaultSagaTransactionalTemplate(); + defaultSagaTransactionalTemplate.setApplicationContext(getApplicationContext()); + defaultSagaTransactionalTemplate.setApplicationId(applicationId); + defaultSagaTransactionalTemplate.setTxServiceGroup(txServiceGroup); + defaultSagaTransactionalTemplate.setAccessKey(accessKey); + defaultSagaTransactionalTemplate.setSecretKey(secretKey); + defaultSagaTransactionalTemplate.afterPropertiesSet(); + sagaTransactionalTemplate = defaultSagaTransactionalTemplate; + } + + dbStateLogStore.setSagaTransactionalTemplate(sagaTransactionalTemplate); + + setStateLogStore(StateLogStoreImpl.wrap(dbStateLogStore)); + } + + if (getStateLangStore() == null) { + DbStateLangStore dbStateLangStore = new DbStateLangStore(); + dbStateLangStore.setDataSource(dataSource); + dbStateLangStore.setTablePrefix(tablePrefix); + dbStateLangStore.setDbType(dbType); + + setStateLangStore(dbStateLangStore); + } + + //must execute after StateLangStore initialized + super.afterPropertiesSet(); + } + + @Override + public void destroy() throws Exception { + if ((sagaTransactionalTemplate != null) && (sagaTransactionalTemplate instanceof DisposableBean)) { + ((DisposableBean) sagaTransactionalTemplate).destroy(); + } + } + + public DataSource getDataSource() { + return dataSource; + } + + public void setDataSource(DataSource dataSource) { + this.dataSource = dataSource; + } + + public String getApplicationId() { + return applicationId; + } + + public void setApplicationId(String applicationId) { + this.applicationId = applicationId; + } + + public String getTxServiceGroup() { + return txServiceGroup; + } + + public void setTxServiceGroup(String txServiceGroup) { + this.txServiceGroup = txServiceGroup; + } + + public String getAccessKey() { + return accessKey; + } + + public void setAccessKey(String accessKey) { + this.accessKey = accessKey; + } + + public String getSecretKey() { + return secretKey; + } + + public void setSecretKey(String secretKey) { + this.secretKey = secretKey; + } + + public void setSagaTransactionalTemplate(SagaTransactionalTemplate sagaTransactionalTemplate) { + this.sagaTransactionalTemplate = sagaTransactionalTemplate; + } + + public String getTablePrefix() { + return tablePrefix; + } + + public void setTablePrefix(String tablePrefix) { + this.tablePrefix = tablePrefix; + } + + public String getDbType() { + return dbType; + } + + public void setDbType(String dbType) { + this.dbType = dbType; + } + + public boolean isRmReportSuccessEnable() { + return rmReportSuccessEnable; + } + + public boolean isSagaBranchRegisterEnable() { + return sagaBranchRegisterEnable; + } + + public void setRmReportSuccessEnable(boolean rmReportSuccessEnable) { + this.rmReportSuccessEnable = rmReportSuccessEnable; + } + + public void setSagaBranchRegisterEnable(boolean sagaBranchRegisterEnable) { + this.sagaBranchRegisterEnable = sagaBranchRegisterEnable; + } } diff --git a/compatible/src/main/java/io/seata/saga/engine/impl/DefaultStateMachineConfig.java b/compatible/src/main/java/io/seata/saga/engine/impl/DefaultStateMachineConfig.java index ab4ce4f348f..d10cf56a16d 100644 --- a/compatible/src/main/java/io/seata/saga/engine/impl/DefaultStateMachineConfig.java +++ b/compatible/src/main/java/io/seata/saga/engine/impl/DefaultStateMachineConfig.java @@ -20,18 +20,20 @@ import io.seata.saga.engine.expression.ExpressionFactoryManager; import io.seata.saga.engine.repo.StateLogRepository; import io.seata.saga.engine.repo.StateMachineRepository; +import io.seata.saga.engine.store.StateLogStore; +import io.seata.saga.engine.store.impl.StateLogStoreImpl; import io.seata.saga.statelang.domain.StateInstance; +import io.seata.saga.statelang.domain.StateMachine; import io.seata.saga.statelang.domain.StateMachineInstance; import io.seata.saga.statelang.domain.impl.StateInstanceImpl; +import io.seata.saga.statelang.domain.impl.StateMachineImpl; import io.seata.saga.statelang.domain.impl.StateMachineInstanceImpl; import org.apache.seata.saga.engine.expression.ExpressionResolver; import org.apache.seata.saga.engine.invoker.ServiceInvokerManager; import org.apache.seata.saga.engine.sequence.SeqGenerator; import org.apache.seata.saga.engine.store.StateLangStore; -import org.apache.seata.saga.engine.store.StateLogStore; import org.apache.seata.saga.engine.strategy.StatusDecisionStrategy; import org.apache.seata.saga.proctrl.eventing.impl.ProcessCtrlEventPublisher; -import org.apache.seata.saga.statelang.domain.StateMachine; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; @@ -45,7 +47,6 @@ /** * Default state machine configuration - * */ public class DefaultStateMachineConfig implements StateMachineConfig, ApplicationContextAware, InitializingBean { @@ -70,11 +71,20 @@ public void afterPropertiesSet() throws Exception { @Override public StateLogStore getStateLogStore() { - return actual.getStateLogStore(); + org.apache.seata.saga.engine.store.StateLogStore stateLogStore = actual.getStateLogStore(); + if (stateLogStore == null) { + return null; + } + + return StateLogStoreImpl.wrap(actual.getStateLogStore()); } public void setStateLogStore(StateLogStore stateLogStore) { - actual.setStateLogStore(stateLogStore); + if (stateLogStore == null) { + actual.setStateLogStore(null); + } else { + actual.setStateLogStore(((StateLogStoreImpl) stateLogStore).unwrap()); + } } @Override @@ -127,32 +137,37 @@ public StateMachineRepository getStateMachineRepository() { return new StateMachineRepository() { @Override public StateMachine getStateMachineById(String stateMachineId) { - return repository.getStateMachineById(stateMachineId); + org.apache.seata.saga.statelang.domain.StateMachine stateMachine = repository.getStateMachineById(stateMachineId); + return StateMachineImpl.wrap(stateMachine); } @Override public StateMachine getStateMachine(String stateMachineName, String tenantId) { - return repository.getStateMachine(stateMachineName, tenantId); + org.apache.seata.saga.statelang.domain.StateMachine stateMachine = repository.getStateMachine(stateMachineName, tenantId); + return StateMachineImpl.wrap(stateMachine); } @Override public StateMachine getStateMachine(String stateMachineName, String tenantId, String version) { - return repository.getStateMachine(stateMachineName, tenantId, version); + org.apache.seata.saga.statelang.domain.StateMachine stateMachine = repository.getStateMachine(stateMachineName, tenantId, version); + return StateMachineImpl.wrap(stateMachine); } @Override public StateMachine registryStateMachine(StateMachine stateMachine) { - return repository.registryStateMachine(stateMachine); + org.apache.seata.saga.statelang.domain.StateMachine unwrap = ((StateMachineImpl) stateMachine).unwrap(); + repository.registryStateMachine(unwrap); + return stateMachine; } @Override - public void registryByResources(InputStream[] resourceAsStreamArray, String tenantId) throws IOException{ + public void registryByResources(InputStream[] resourceAsStreamArray, String tenantId) throws IOException { repository.registryByResources(resourceAsStreamArray, tenantId); } }; } - public void setStateMachineRepository(StateMachineRepository stateMachineRepository) { + public void setStateMachineRepository(org.apache.seata.saga.engine.repo.StateMachineRepository stateMachineRepository) { actual.setStateMachineRepository(stateMachineRepository); } diff --git a/compatible/src/main/java/io/seata/saga/engine/repo/StateMachineRepository.java b/compatible/src/main/java/io/seata/saga/engine/repo/StateMachineRepository.java index f02515c0905..7d83cc369f7 100644 --- a/compatible/src/main/java/io/seata/saga/engine/repo/StateMachineRepository.java +++ b/compatible/src/main/java/io/seata/saga/engine/repo/StateMachineRepository.java @@ -16,10 +16,58 @@ */ package io.seata.saga.engine.repo; + +import io.seata.saga.statelang.domain.StateMachine; + +import java.io.IOException; +import java.io.InputStream; + /** * StateMachineRepository - * */ -public interface StateMachineRepository extends org.apache.seata.saga.engine.repo.StateMachineRepository { +public interface StateMachineRepository { + + /** + * Gets get state machine by id. + * + * @param stateMachineId the state machine id + * @return the get state machine by id + */ + StateMachine getStateMachineById(String stateMachineId); + + /** + * Gets get state machine. + * + * @param stateMachineName the state machine name + * @param tenantId the tenant id + * @return the get state machine + */ + StateMachine getStateMachine(String stateMachineName, String tenantId); + + /** + * Gets get state machine. + * + * @param stateMachineName the state machine name + * @param tenantId the tenant id + * @param version the version + * @return the get state machine + */ + StateMachine getStateMachine(String stateMachineName, String tenantId, String version); + + /** + * Register the state machine to the repository (if the same version already exists, return the existing version) + * + * @param stateMachine stateMachine + * @return the state machine + */ + StateMachine registryStateMachine(StateMachine stateMachine); + /** + * Registry by resources. + * + * @param resourceAsStreamArray the resource as stream array + * @param tenantId the tenant id + * @throws IOException the io exception + */ + void registryByResources(InputStream[] resourceAsStreamArray, String tenantId) throws IOException; } diff --git a/compatible/src/main/java/io/seata/saga/engine/store/StateLogStore.java b/compatible/src/main/java/io/seata/saga/engine/store/StateLogStore.java new file mode 100644 index 00000000000..0c664e1ef05 --- /dev/null +++ b/compatible/src/main/java/io/seata/saga/engine/store/StateLogStore.java @@ -0,0 +1,114 @@ +/* + * 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.saga.engine.store; + + +import io.seata.saga.proctrl.ProcessContext; +import io.seata.saga.statelang.domain.StateInstance; +import io.seata.saga.statelang.domain.StateMachineInstance; + +import java.util.List; + +public interface StateLogStore { + + /** + * Record state machine startup events + * + * @param machineInstance the state machine instance + * @param context the state machine process context + */ + void recordStateMachineStarted(StateMachineInstance machineInstance, ProcessContext context); + + /** + * Record status end event + * + * @param machineInstance the state machine instance + * @param context the state machine process context + */ + void recordStateMachineFinished(StateMachineInstance machineInstance, ProcessContext context); + + /** + * Record state machine restarted + * + * @param machineInstance the state machine instance + * @param context the state machine process context + */ + void recordStateMachineRestarted(StateMachineInstance machineInstance, ProcessContext context); + + /** + * Record state start execution event + * + * @param stateInstance the state machine instance + * @param context the state machine process context + */ + void recordStateStarted(StateInstance stateInstance, ProcessContext context); + + /** + * Record state execution end event + * + * @param stateInstance the state machine instance + * @param context the state machine process context + */ + void recordStateFinished(StateInstance stateInstance, ProcessContext context); + + /** + * Get state machine instance + * + * @param stateMachineInstanceId the state machine instance id + * @return the state machine instance + */ + StateMachineInstance getStateMachineInstance(String stateMachineInstanceId); + + /** + * Get state machine instance by businessKey + * + * @param businessKey the businessKey + * @param tenantId the tenant id + * @return state machine message + */ + StateMachineInstance getStateMachineInstanceByBusinessKey(String businessKey, String tenantId); + + /** + * Query the list of state machine instances by parent id + * + * @param parentId the state machine parent's id + * @return the state machine instance list + */ + List queryStateMachineInstanceByParentId(String parentId); + + /** + * Get state instance + * + * @param stateInstanceId the state instance id + * @param machineInstId the machine instance id + * @return state instance message + */ + StateInstance getStateInstance(String stateInstanceId, String machineInstId); + + /** + * Get a list of state instances by state machine instance id + * + * @param stateMachineInstanceId the state machine instance id + * @return state instance list + */ + List queryStateInstanceListByMachineInstanceId(String stateMachineInstanceId); + + /** + * clear the LocalThread + */ + void clearUp(ProcessContext context); +} diff --git a/compatible/src/main/java/io/seata/saga/engine/store/impl/StateLogStoreImpl.java b/compatible/src/main/java/io/seata/saga/engine/store/impl/StateLogStoreImpl.java new file mode 100644 index 00000000000..54c89c6b1bb --- /dev/null +++ b/compatible/src/main/java/io/seata/saga/engine/store/impl/StateLogStoreImpl.java @@ -0,0 +1,116 @@ +/* + * 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.saga.engine.store.impl; + +import io.seata.saga.engine.store.StateLogStore; +import io.seata.saga.proctrl.ProcessContext; +import io.seata.saga.proctrl.impl.ProcessContextImpl; +import io.seata.saga.statelang.domain.StateInstance; +import io.seata.saga.statelang.domain.StateMachineInstance; +import io.seata.saga.statelang.domain.impl.StateInstanceImpl; +import io.seata.saga.statelang.domain.impl.StateMachineInstanceImpl; +import org.apache.seata.common.util.CollectionUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +public class StateLogStoreImpl implements StateLogStore { + + private final org.apache.seata.saga.engine.store.StateLogStore actual; + + private StateLogStoreImpl(org.apache.seata.saga.engine.store.StateLogStore actual) { + this.actual = actual; + } + + @Override + public void recordStateMachineStarted(StateMachineInstance machineInstance, ProcessContext context) { + actual.recordStateMachineStarted(((StateMachineInstanceImpl) machineInstance).unwrap(), ((ProcessContextImpl) context).unwrap()); + } + + @Override + public void recordStateMachineFinished(StateMachineInstance machineInstance, ProcessContext context) { + actual.recordStateMachineFinished(((StateMachineInstanceImpl) machineInstance).unwrap(), ((ProcessContextImpl) context).unwrap()); + } + + @Override + public void recordStateMachineRestarted(StateMachineInstance machineInstance, ProcessContext context) { + actual.recordStateMachineRestarted(((StateMachineInstanceImpl) machineInstance).unwrap(), ((ProcessContextImpl) context).unwrap()); + } + + @Override + public void recordStateStarted(StateInstance stateInstance, ProcessContext context) { + actual.recordStateStarted(((StateInstanceImpl) stateInstance).unwrap(), ((ProcessContextImpl) context).unwrap()); + } + + @Override + public void recordStateFinished(StateInstance stateInstance, ProcessContext context) { + actual.recordStateFinished(((StateInstanceImpl) stateInstance).unwrap(), ((ProcessContextImpl) context).unwrap()); + } + + @Override + public StateMachineInstance getStateMachineInstance(String stateMachineInstanceId) { + org.apache.seata.saga.statelang.domain.StateMachineInstance stateMachineInstance = actual.getStateMachineInstance(stateMachineInstanceId); + return StateMachineInstanceImpl.wrap(stateMachineInstance); + } + + @Override + public StateMachineInstance getStateMachineInstanceByBusinessKey(String businessKey, String tenantId) { + org.apache.seata.saga.statelang.domain.StateMachineInstance stateMachineInstance = actual.getStateMachineInstanceByBusinessKey(businessKey, tenantId); + return StateMachineInstanceImpl.wrap(stateMachineInstance); + } + + @Override + public List queryStateMachineInstanceByParentId(String parentId) { + List stateMachineInstances = actual.queryStateMachineInstanceByParentId(parentId); + if (CollectionUtils.isEmpty(stateMachineInstances)) { + return new ArrayList<>(); + } + return stateMachineInstances.stream().map(StateMachineInstanceImpl::wrap).collect(Collectors.toList()); + } + + @Override + public StateInstance getStateInstance(String stateInstanceId, String machineInstId) { + org.apache.seata.saga.statelang.domain.StateInstance stateInstance = actual.getStateInstance(stateInstanceId, machineInstId); + if (stateInstance == null) { + return null; + } + return StateInstanceImpl.wrap(stateInstance); + } + + @Override + public List queryStateInstanceListByMachineInstanceId(String stateMachineInstanceId) { + List stateInstances = actual.queryStateInstanceListByMachineInstanceId(stateMachineInstanceId); + if (CollectionUtils.isEmpty(stateInstances)) { + return new ArrayList<>(); + } + return stateInstances.stream().map(StateInstanceImpl::wrap).collect(Collectors.toList()); + } + + @Override + public void clearUp(ProcessContext context) { + actual.clearUp(((ProcessContextImpl) context).unwrap()); + } + + public static StateLogStore wrap(org.apache.seata.saga.engine.store.StateLogStore actual) { + return new StateLogStoreImpl(actual); + } + + public org.apache.seata.saga.engine.store.StateLogStore unwrap() { + return actual; + } +} diff --git a/compatible/src/main/java/io/seata/saga/rm/SagaResourceManager.java b/compatible/src/main/java/io/seata/saga/rm/SagaResourceManager.java new file mode 100644 index 00000000000..a850e40605d --- /dev/null +++ b/compatible/src/main/java/io/seata/saga/rm/SagaResourceManager.java @@ -0,0 +1,161 @@ +/* + * 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.saga.rm; + +import io.seata.saga.statelang.domain.ExecutionStatus; +import io.seata.saga.statelang.domain.StateMachineInstance; +import org.apache.seata.common.exception.FrameworkErrorCode; +import org.apache.seata.core.exception.TransactionException; +import org.apache.seata.core.model.BranchStatus; +import org.apache.seata.core.model.BranchType; +import org.apache.seata.core.model.GlobalStatus; +import org.apache.seata.core.model.Resource; +import org.apache.seata.rm.AbstractResourceManager; +import org.apache.seata.saga.engine.exception.EngineExecutionException; +import org.apache.seata.saga.engine.exception.ForwardInvalidException; +import org.apache.seata.saga.rm.SagaResource; +import org.apache.seata.saga.statelang.domain.RecoverStrategy; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class SagaResourceManager extends AbstractResourceManager { + + private static final Logger LOGGER = LoggerFactory.getLogger(SagaResourceManager.class); + + /** + * Saga resource cache + */ + private Map sagaResourceCache = new ConcurrentHashMap<>(); + + /** + * Instantiates a new saga resource manager. + */ + public SagaResourceManager() { + } + + /** + * registry saga resource + * + * @param resource The resource to be managed. + */ + @Override + public void registerResource(Resource resource) { + SagaResource sagaResource = (SagaResource)resource; + sagaResourceCache.put(sagaResource.getResourceId(), sagaResource); + super.registerResource(sagaResource); + } + + @Override + public Map getManagedResources() { + return sagaResourceCache; + } + + /** + * SAGA branch commit + * + * @param branchType the branch type + * @param xid Transaction id. + * @param branchId Branch id. + * @param resourceId Resource id. + * @param applicationData Application data bind with this branch. + * @return the branch status + * @throws TransactionException the transaction exception + */ + @Override + public BranchStatus branchCommit(BranchType branchType, String xid, long branchId, String resourceId, + String applicationData) throws TransactionException { + try { + StateMachineInstance machineInstance = StateMachineEngineHolder.getStateMachineEngine().forward(xid, null); + + if (ExecutionStatus.SU.equals(machineInstance.getStatus()) + && machineInstance.getCompensationStatus() == null) { + return BranchStatus.PhaseTwo_Committed; + } else if (ExecutionStatus.SU.equals(machineInstance.getCompensationStatus())) { + return BranchStatus.PhaseTwo_Rollbacked; + } else if (ExecutionStatus.FA.equals(machineInstance.getCompensationStatus()) || ExecutionStatus.UN.equals( + machineInstance.getCompensationStatus())) { + return BranchStatus.PhaseTwo_RollbackFailed_Retryable; + } else if (ExecutionStatus.FA.equals(machineInstance.getStatus()) + && machineInstance.getCompensationStatus() == null) { + return BranchStatus.PhaseOne_Failed; + } + + } catch (ForwardInvalidException e) { + LOGGER.error("StateMachine forward failed, xid: " + xid, e); + + //if StateMachineInstanceNotExists stop retry + if (FrameworkErrorCode.StateMachineInstanceNotExists.equals(e.getErrcode())) { + return BranchStatus.PhaseTwo_Committed; + } + } catch (Exception e) { + LOGGER.error("StateMachine forward failed, xid: " + xid, e); + } + return BranchStatus.PhaseTwo_CommitFailed_Retryable; + } + + /** + * SAGA branch rollback + * + * @param branchType the branch type + * @param xid Transaction id. + * @param branchId Branch id. + * @param resourceId Resource id. + * @param applicationData Application data bind with this branch. + * @return the branch status + * @throws TransactionException the transaction exception + */ + @Override + public BranchStatus branchRollback(BranchType branchType, String xid, long branchId, String resourceId, + String applicationData) throws TransactionException { + try { + StateMachineInstance stateMachineInstance = StateMachineEngineHolder.getStateMachineEngine().reloadStateMachineInstance(xid); + if (stateMachineInstance == null) { + return BranchStatus.PhaseTwo_Rollbacked; + } + if (RecoverStrategy.Forward.equals(stateMachineInstance.getStateMachine().getRecoverStrategy()) + && (GlobalStatus.TimeoutRollbacking.name().equals(applicationData) + || GlobalStatus.TimeoutRollbackRetrying.name().equals(applicationData))) { + LOGGER.warn("Retry by custom recover strategy [Forward] on timeout, SAGA global[{}]", xid); + return BranchStatus.PhaseTwo_CommitFailed_Retryable; + } + + stateMachineInstance = StateMachineEngineHolder.getStateMachineEngine().compensate(xid, + null); + if (ExecutionStatus.SU.equals(stateMachineInstance.getCompensationStatus())) { + return BranchStatus.PhaseTwo_Rollbacked; + } + } catch (EngineExecutionException e) { + LOGGER.error("StateMachine compensate failed, xid: " + xid, e); + + //if StateMachineInstanceNotExists stop retry + if (FrameworkErrorCode.StateMachineInstanceNotExists.equals(e.getErrcode())) { + return BranchStatus.PhaseTwo_Rollbacked; + } + } catch (Exception e) { + LOGGER.error("StateMachine compensate failed, xid: " + xid, e); + } + return BranchStatus.PhaseTwo_RollbackFailed_Retryable; + } + + @Override + public BranchType getBranchType() { + return BranchType.SAGA; + } +} diff --git a/compatible/src/main/java/io/seata/integration/tx/api/util/ProxyUtil.java b/compatible/src/main/java/io/seata/saga/rm/StateMachineEngineHolder.java similarity index 66% rename from compatible/src/main/java/io/seata/integration/tx/api/util/ProxyUtil.java rename to compatible/src/main/java/io/seata/saga/rm/StateMachineEngineHolder.java index 211ccbb89af..1866e34fd54 100644 --- a/compatible/src/main/java/io/seata/integration/tx/api/util/ProxyUtil.java +++ b/compatible/src/main/java/io/seata/saga/rm/StateMachineEngineHolder.java @@ -14,8 +14,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.seata.integration.tx.api.util; +package io.seata.saga.rm; -@Deprecated -public class ProxyUtil extends org.apache.seata.integration.tx.api.util.ProxyUtil{ + +import io.seata.saga.engine.StateMachineEngine; + +public class StateMachineEngineHolder { + + private static StateMachineEngine stateMachineEngine; + + public static StateMachineEngine getStateMachineEngine() { + return stateMachineEngine; + } + + public static void setStateMachineEngine(StateMachineEngine smEngine) { + stateMachineEngine = smEngine; + } } diff --git a/compatible/src/main/java/io/seata/saga/statelang/domain/ExecutionStatus.java b/compatible/src/main/java/io/seata/saga/statelang/domain/ExecutionStatus.java index 388e6bcf934..06b6632eefd 100644 --- a/compatible/src/main/java/io/seata/saga/statelang/domain/ExecutionStatus.java +++ b/compatible/src/main/java/io/seata/saga/statelang/domain/ExecutionStatus.java @@ -58,6 +58,9 @@ public String getStatusString() { } public static ExecutionStatus wrap(org.apache.seata.saga.statelang.domain.ExecutionStatus target) { + if(target == null){ + return null; + } switch (target) { case RU: return RU; diff --git a/compatible/src/main/java/io/seata/saga/statelang/domain/RecoverStrategy.java b/compatible/src/main/java/io/seata/saga/statelang/domain/RecoverStrategy.java new file mode 100644 index 00000000000..dfd07747371 --- /dev/null +++ b/compatible/src/main/java/io/seata/saga/statelang/domain/RecoverStrategy.java @@ -0,0 +1,58 @@ +/* + * 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.saga.statelang.domain; + +/** + * Recover Strategy + */ +public enum RecoverStrategy { + + /** + * Compensate + */ + Compensate, + + /** + * Forward + */ + Forward; + + public static RecoverStrategy wrap(org.apache.seata.saga.statelang.domain.RecoverStrategy target) { + if (target == null) { + return null; + } + switch (target) { + case Compensate: + return Compensate; + case Forward: + return Forward; + default: + throw new IllegalArgumentException("Cannot convert " + target.name()); + } + } + + public org.apache.seata.saga.statelang.domain.RecoverStrategy unwrap() { + switch (this) { + case Compensate: + return org.apache.seata.saga.statelang.domain.RecoverStrategy.Compensate; + case Forward: + return org.apache.seata.saga.statelang.domain.RecoverStrategy.Forward; + default: + throw new IllegalArgumentException("Cannot convert " + this.name()); + } + } +} diff --git a/compatible/src/main/java/io/seata/saga/statelang/domain/State.java b/compatible/src/main/java/io/seata/saga/statelang/domain/State.java new file mode 100644 index 00000000000..9cf71f5391f --- /dev/null +++ b/compatible/src/main/java/io/seata/saga/statelang/domain/State.java @@ -0,0 +1,64 @@ +/* + * 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.saga.statelang.domain; + +import java.util.Map; + +public interface State { + + /** + * name + * + * @return the state name + */ + String getName(); + + /** + * comment + * + * @return the state comment + */ + String getComment(); + + /** + * type + * + * @return the state type + */ + String getType(); + + /** + * next state name + * + * @return the next state name + */ + String getNext(); + + /** + * extension properties + * + * @return the state extensions + */ + Map getExtensions(); + + /** + * state machine instance + * + * @return the state machine + */ + StateMachine getStateMachine(); +} \ No newline at end of file diff --git a/compatible/src/main/java/io/seata/saga/statelang/domain/StateMachine.java b/compatible/src/main/java/io/seata/saga/statelang/domain/StateMachine.java new file mode 100644 index 00000000000..15f8045212f --- /dev/null +++ b/compatible/src/main/java/io/seata/saga/statelang/domain/StateMachine.java @@ -0,0 +1,223 @@ +/* + * 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.saga.statelang.domain; + +import java.util.Date; +import java.util.Map; + +public interface StateMachine { + + /** + * name + * + * @return the state machine name + */ + String getName(); + + /** + * comment + * + * @return the state machine comment + */ + String getComment(); + + /** + * start state name + * + * @return the start state name + */ + String getStartState(); + + void setStartState(String startState); + + /** + * version + * + * @return the state machine version + */ + String getVersion(); + + /** + * set version + * + * @param version the state machine version + */ + void setVersion(String version); + + /** + * states + * + * @return the state machine key: the state machine name,value: the state machine + */ + Map getStates(); + + /** + * get state + * + * @param name the state machine name + * @return the state machine + */ + State getState(String name); + + /** + * get id + * + * @return the state machine id + */ + String getId(); + + void setId(String id); + + /** + * get tenantId + * + * @return the tenant id + */ + String getTenantId(); + + /** + * set tenantId + * + * @param tenantId the tenant id + */ + void setTenantId(String tenantId); + + /** + * app name + * + * @return the app name + */ + String getAppName(); + + /** + * type, there is only one type: SSL(SEATA state language) + * + * @return the state type + */ + String getType(); + + /** + * statue (Active|Inactive) + * + * @return the state machine status + */ + Status getStatus(); + + /** + * recover strategy: prefer compensation or forward when error occurred + * + * @return the recover strategy + */ + RecoverStrategy getRecoverStrategy(); + + /** + * set RecoverStrategy + * + * @param recoverStrategy the recover strategy + */ + void setRecoverStrategy(RecoverStrategy recoverStrategy); + + /** + * Is it persist execution log to storage?, default true + * + * @return is persist + */ + boolean isPersist(); + + /** + * Is update last retry execution log, default append new + * + * @return the boolean + */ + Boolean isRetryPersistModeUpdate(); + + /** + * Is update last compensate execution log, default append new + * + * @return the boolean + */ + Boolean isCompensatePersistModeUpdate(); + + /** + * State language text + * + * @return the state language text + */ + String getContent(); + + void setContent(String content); + + /** + * get create time + * + * @return the create gmt + */ + Date getGmtCreate(); + + /** + * set create time + * + * @param date the create gmt + */ + void setGmtCreate(Date date); + + enum Status { + /** + * Active + */ + AC("Active"), + /** + * Inactive + */ + IN("Inactive"); + + private final String statusString; + + Status(String statusString) { + this.statusString = statusString; + } + + public String getStatusString() { + return statusString; + } + + public static Status wrap(org.apache.seata.saga.statelang.domain.StateMachine.Status target) { + if (target == null) { + return null; + } + switch (target) { + case AC: + return AC; + case IN: + return IN; + default: + throw new IllegalArgumentException("Cannot convert " + target.name()); + } + } + + public org.apache.seata.saga.statelang.domain.StateMachine.Status unwrap() { + switch (this) { + case AC: + return org.apache.seata.saga.statelang.domain.StateMachine.Status.AC; + case IN: + return org.apache.seata.saga.statelang.domain.StateMachine.Status.IN; + default: + throw new IllegalArgumentException("Cannot convert " + this.name()); + } + } + } +} \ No newline at end of file diff --git a/compatible/src/main/java/io/seata/saga/statelang/domain/StateMachineInstance.java b/compatible/src/main/java/io/seata/saga/statelang/domain/StateMachineInstance.java index 0ce42b0a999..b4b8efec3b1 100644 --- a/compatible/src/main/java/io/seata/saga/statelang/domain/StateMachineInstance.java +++ b/compatible/src/main/java/io/seata/saga/statelang/domain/StateMachineInstance.java @@ -16,8 +16,6 @@ */ package io.seata.saga.statelang.domain; -import org.apache.seata.saga.statelang.domain.StateMachine; - import java.util.Date; import java.util.List; import java.util.Map; diff --git a/compatible/src/main/java/io/seata/saga/statelang/domain/impl/StateImpl.java b/compatible/src/main/java/io/seata/saga/statelang/domain/impl/StateImpl.java new file mode 100644 index 00000000000..bdc5ec3b36e --- /dev/null +++ b/compatible/src/main/java/io/seata/saga/statelang/domain/impl/StateImpl.java @@ -0,0 +1,74 @@ +/* + * 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.saga.statelang.domain.impl; + +import io.seata.saga.statelang.domain.State; +import io.seata.saga.statelang.domain.StateMachine; + +import java.util.Map; + +public class StateImpl implements State { + + private final org.apache.seata.saga.statelang.domain.State actual; + + private StateImpl(org.apache.seata.saga.statelang.domain.State actual) { + this.actual = actual; + } + + + @Override + public String getName() { + return actual.getName(); + } + + @Override + public String getComment() { + return actual.getComment(); + } + + @Override + public String getType() { + return actual.getType(); + } + + @Override + public String getNext() { + return actual.getNext(); + } + + @Override + public Map getExtensions() { + return actual.getExtensions(); + } + + @Override + public StateMachine getStateMachine() { + org.apache.seata.saga.statelang.domain.StateMachine stateMachine = actual.getStateMachine(); + return StateMachineImpl.wrap(stateMachine); + } + + public static StateImpl wrap(org.apache.seata.saga.statelang.domain.State target) { + if (target == null) { + return null; + } + return new StateImpl(target); + } + + public org.apache.seata.saga.statelang.domain.State unwrap() { + return actual; + } +} diff --git a/compatible/src/main/java/io/seata/saga/statelang/domain/impl/StateInstanceImpl.java b/compatible/src/main/java/io/seata/saga/statelang/domain/impl/StateInstanceImpl.java index be52a688a77..25dd81ee60f 100644 --- a/compatible/src/main/java/io/seata/saga/statelang/domain/impl/StateInstanceImpl.java +++ b/compatible/src/main/java/io/seata/saga/statelang/domain/impl/StateInstanceImpl.java @@ -153,7 +153,7 @@ public boolean isForUpdate() { @Override public void setForUpdate(boolean forUpdate) { - setForUpdate(forUpdate); + actual.setForUpdate(forUpdate); } @Override @@ -213,7 +213,11 @@ public ExecutionStatus getStatus() { @Override public void setStatus(ExecutionStatus status) { - actual.setStatus(status.unwrap()); + if(status == null){ + actual.setStatus(null); + }else { + actual.setStatus(status.unwrap()); + } } @Override diff --git a/compatible/src/main/java/io/seata/saga/statelang/domain/impl/StateMachineImpl.java b/compatible/src/main/java/io/seata/saga/statelang/domain/impl/StateMachineImpl.java new file mode 100644 index 00000000000..f3bdc2f2b10 --- /dev/null +++ b/compatible/src/main/java/io/seata/saga/statelang/domain/impl/StateMachineImpl.java @@ -0,0 +1,179 @@ +/* + * 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.saga.statelang.domain.impl; + +import io.seata.saga.statelang.domain.RecoverStrategy; +import io.seata.saga.statelang.domain.State; +import io.seata.saga.statelang.domain.StateMachine; + +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.Map; + +public class StateMachineImpl implements StateMachine { + + private final org.apache.seata.saga.statelang.domain.StateMachine actual; + + private StateMachineImpl(org.apache.seata.saga.statelang.domain.StateMachine actual) { + this.actual = actual; + } + + @Override + public String getName() { + return actual.getName(); + } + + @Override + public String getComment() { + return actual.getComment(); + } + + @Override + public String getStartState() { + return actual.getStartState(); + } + + @Override + public void setStartState(String startState) { + actual.setStartState(startState); + } + + @Override + public String getVersion() { + return actual.getVersion(); + } + + @Override + public void setVersion(String version) { + actual.setVersion(version); + } + + @Override + public Map getStates() { + Map states = actual.getStates(); + if (states == null) { + return null; + } + + Map resultMap = new LinkedHashMap<>(); + for (Map.Entry entry : states.entrySet()) { + org.apache.seata.saga.statelang.domain.State state = entry.getValue(); + resultMap.put(entry.getKey(), StateImpl.wrap(state)); + } + return resultMap; + } + + @Override + public State getState(String name) { + org.apache.seata.saga.statelang.domain.State state = actual.getState(name); + return StateImpl.wrap(state); + } + + @Override + public String getId() { + return actual.getId(); + } + + @Override + public void setId(String id) { + actual.setId(id); + } + + @Override + public String getTenantId() { + return actual.getTenantId(); + } + + @Override + public void setTenantId(String tenantId) { + actual.setTenantId(tenantId); + } + + @Override + public String getAppName() { + return actual.getAppName(); + } + + @Override + public String getType() { + return actual.getType(); + } + + @Override + public Status getStatus() { + org.apache.seata.saga.statelang.domain.StateMachine.Status status = actual.getStatus(); + return Status.wrap(status); + } + + @Override + public RecoverStrategy getRecoverStrategy() { + org.apache.seata.saga.statelang.domain.RecoverStrategy recoverStrategy = actual.getRecoverStrategy(); + return RecoverStrategy.wrap(recoverStrategy); + } + + @Override + public void setRecoverStrategy(RecoverStrategy recoverStrategy) { + org.apache.seata.saga.statelang.domain.RecoverStrategy unwrap = recoverStrategy.unwrap(); + actual.setRecoverStrategy(unwrap); + } + + @Override + public boolean isPersist() { + return actual.isPersist(); + } + + @Override + public Boolean isRetryPersistModeUpdate() { + return actual.isRetryPersistModeUpdate(); + } + + @Override + public Boolean isCompensatePersistModeUpdate() { + return actual.isCompensatePersistModeUpdate(); + } + + @Override + public String getContent() { + return actual.getContent(); + } + + @Override + public void setContent(String content) { + actual.setContent(content); + } + + @Override + public Date getGmtCreate() { + return actual.getGmtCreate(); + } + + @Override + public void setGmtCreate(Date date) { + actual.setGmtCreate(date); + } + + public static StateMachineImpl wrap(org.apache.seata.saga.statelang.domain.StateMachine target) { + if (target == null) { + return null; + } + return new StateMachineImpl(target); + } + + public org.apache.seata.saga.statelang.domain.StateMachine unwrap() { + return actual; + } +} diff --git a/compatible/src/main/java/io/seata/saga/statelang/domain/impl/StateMachineInstanceImpl.java b/compatible/src/main/java/io/seata/saga/statelang/domain/impl/StateMachineInstanceImpl.java index 4d04d00296e..57e2ad83781 100644 --- a/compatible/src/main/java/io/seata/saga/statelang/domain/impl/StateMachineInstanceImpl.java +++ b/compatible/src/main/java/io/seata/saga/statelang/domain/impl/StateMachineInstanceImpl.java @@ -18,8 +18,8 @@ import io.seata.saga.statelang.domain.ExecutionStatus; import io.seata.saga.statelang.domain.StateInstance; +import io.seata.saga.statelang.domain.StateMachine; import io.seata.saga.statelang.domain.StateMachineInstance; -import org.apache.seata.saga.statelang.domain.StateMachine; import java.util.AbstractMap; import java.util.Date; @@ -111,7 +111,11 @@ public ExecutionStatus getStatus() { @Override public void setStatus(ExecutionStatus status) { - actual.setStatus(status.unwrap()); + if (status == null) { + actual.setStatus(null); + } else { + actual.setStatus(status.unwrap()); + } } @Override @@ -121,7 +125,11 @@ public ExecutionStatus getCompensationStatus() { @Override public void setCompensationStatus(ExecutionStatus compensationStatus) { - actual.setCompensationStatus(compensationStatus.unwrap()); + if (compensationStatus == null) { + actual.setCompensationStatus(null); + } else { + actual.setCompensationStatus(compensationStatus.unwrap()); + } } @Override @@ -196,12 +204,13 @@ public void setContext(Map context) { @Override public StateMachine getStateMachine() { - return actual.getStateMachine(); + return StateMachineImpl.wrap(actual.getStateMachine()); } @Override public void setStateMachine(StateMachine stateMachine) { - actual.setStateMachine(stateMachine); + org.apache.seata.saga.statelang.domain.StateMachine unwrap = ((StateMachineImpl) stateMachine).unwrap(); + actual.setStateMachine(unwrap); } @Override diff --git a/compatible/src/main/java/io/seata/spring/annotation/GlobalTransactionScanner.java b/compatible/src/main/java/io/seata/spring/annotation/GlobalTransactionScanner.java new file mode 100644 index 00000000000..f242db672c2 --- /dev/null +++ b/compatible/src/main/java/io/seata/spring/annotation/GlobalTransactionScanner.java @@ -0,0 +1,86 @@ +/* + * 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.util.StringUtils; +import io.seata.rm.RMClient; +import io.seata.tm.TMClient; +import io.seata.tm.api.FailureHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.apache.seata.common.DefaultValues.DEFAULT_TX_GROUP; +import static org.apache.seata.common.DefaultValues.DEFAULT_TX_GROUP_OLD; + +public class GlobalTransactionScanner extends org.apache.seata.spring.annotation.GlobalTransactionScanner { + + private static final Logger LOGGER = LoggerFactory.getLogger(GlobalTransactionScanner.class); + + public GlobalTransactionScanner(String txServiceGroup) { + super(txServiceGroup); + } + + public GlobalTransactionScanner(String txServiceGroup, int mode) { + super(txServiceGroup, mode); + } + + public GlobalTransactionScanner(String applicationId, String txServiceGroup) { + super(applicationId, txServiceGroup); + } + + public GlobalTransactionScanner(String applicationId, String txServiceGroup, int mode) { + super(applicationId, txServiceGroup, mode); + } + + public GlobalTransactionScanner(String applicationId, String txServiceGroup, FailureHandler failureHandlerHook) { + super(applicationId, txServiceGroup, failureHandlerHook); + } + + public GlobalTransactionScanner(String applicationId, String txServiceGroup, int mode, FailureHandler failureHandlerHook) { + super(applicationId, txServiceGroup, mode, failureHandlerHook); + } + + protected void initClient() { + if (LOGGER.isInfoEnabled()) { + LOGGER.info("Initializing Global Transaction Clients ... "); + } + if (DEFAULT_TX_GROUP_OLD.equals(getTxServiceGroup())) { + LOGGER.warn("the default value of seata.tx-service-group: {} has already changed to {} since Seata 1.5, " + + "please change your default configuration as soon as possible " + + "and we don't recommend you to use default tx-service-group's value provided by seata", + DEFAULT_TX_GROUP_OLD, DEFAULT_TX_GROUP); + } + if (StringUtils.isNullOrEmpty(getApplicationId()) || StringUtils.isNullOrEmpty(getTxServiceGroup())) { + throw new IllegalArgumentException(String.format("applicationId: %s, txServiceGroup: %s", getApplicationId(), getTxServiceGroup())); + } + //init TM + TMClient.init(getApplicationId(), getTxServiceGroup(), getAccessKey(), getSecretKey()); + if (LOGGER.isInfoEnabled()) { + LOGGER.info("Transaction Manager Client is initialized. applicationId[{}] txServiceGroup[{}]", getApplicationId(), getTxServiceGroup()); + } + //init RM + RMClient.init(getApplicationId(), getTxServiceGroup()); + if (LOGGER.isInfoEnabled()) { + LOGGER.info("Resource Manager is initialized. applicationId[{}] txServiceGroup[{}]", getApplicationId(), getTxServiceGroup()); + } + + if (LOGGER.isInfoEnabled()) { + LOGGER.info("Global Transaction Clients are initialized. "); + } + registerSpringShutdownHook(); + } +} diff --git a/compatible/src/main/java/io/seata/spring/annotation/datasource/AutoDataSourceProxyRegistrar.java b/compatible/src/main/java/io/seata/spring/annotation/datasource/AutoDataSourceProxyRegistrar.java new file mode 100644 index 00000000000..1fbc4f5f06c --- /dev/null +++ b/compatible/src/main/java/io/seata/spring/annotation/datasource/AutoDataSourceProxyRegistrar.java @@ -0,0 +1,54 @@ +/* + * 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.datasource; + +import java.util.Map; + +import org.apache.seata.spring.annotation.datasource.SeataAutoDataSourceProxyCreator; +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; +import org.springframework.core.type.AnnotationMetadata; + +public class AutoDataSourceProxyRegistrar implements ImportBeanDefinitionRegistrar { + private static final String ATTRIBUTE_KEY_USE_JDK_PROXY = "useJdkProxy"; + private static final String ATTRIBUTE_KEY_EXCLUDES = "excludes"; + private static final String ATTRIBUTE_KEY_DATA_SOURCE_PROXY_MODE = "dataSourceProxyMode"; + + public static final String BEAN_NAME_SEATA_AUTO_DATA_SOURCE_PROXY_CREATOR = "seataAutoDataSourceProxyCreator"; + + @Override + public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { + Map annotationAttributes = importingClassMetadata.getAnnotationAttributes(EnableAutoDataSourceProxy.class.getName()); + + boolean useJdkProxy = Boolean.parseBoolean(annotationAttributes.get(ATTRIBUTE_KEY_USE_JDK_PROXY).toString()); + String[] excludes = (String[]) annotationAttributes.get(ATTRIBUTE_KEY_EXCLUDES); + String dataSourceProxyMode = (String) annotationAttributes.get(ATTRIBUTE_KEY_DATA_SOURCE_PROXY_MODE); + + //register seataAutoDataSourceProxyCreator bean def + if (!registry.containsBeanDefinition(BEAN_NAME_SEATA_AUTO_DATA_SOURCE_PROXY_CREATOR)) { + AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder + .genericBeanDefinition(SeataAutoDataSourceProxyCreator.class) + .addConstructorArgValue(useJdkProxy) + .addConstructorArgValue(excludes) + .addConstructorArgValue(dataSourceProxyMode) + .getBeanDefinition(); + registry.registerBeanDefinition(BEAN_NAME_SEATA_AUTO_DATA_SOURCE_PROXY_CREATOR, beanDefinition); + } + } +} diff --git a/compatible/src/main/java/io/seata/spring/annotation/datasource/EnableAutoDataSourceProxy.java b/compatible/src/main/java/io/seata/spring/annotation/datasource/EnableAutoDataSourceProxy.java new file mode 100644 index 00000000000..44e96086848 --- /dev/null +++ b/compatible/src/main/java/io/seata/spring/annotation/datasource/EnableAutoDataSourceProxy.java @@ -0,0 +1,55 @@ +/* + * 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.datasource; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.context.annotation.Import; + +/** + * This annotation will enable auto proxying of datasource bean. + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Import(AutoDataSourceProxyRegistrar.class) +@Documented +public @interface EnableAutoDataSourceProxy { + /** + * Whether use JDK proxy instead of CGLIB proxy + * + * @return useJdkProxy + */ + boolean useJdkProxy() default false; + + /** + * Specifies which datasource bean are not eligible for auto-proxying + * + * @return excludes + */ + String[] excludes() default {}; + + /** + * Data source proxy mode, AT or XA + * + * @return dataSourceProxyMode + */ + String dataSourceProxyMode() default "AT"; +} diff --git a/compatible/src/main/resources/META-INF/services/io.seata.core.model.ResourceManager b/compatible/src/main/resources/META-INF/services/io.seata.core.model.ResourceManager new file mode 100644 index 00000000000..4ec5cba1d35 --- /dev/null +++ b/compatible/src/main/resources/META-INF/services/io.seata.core.model.ResourceManager @@ -0,0 +1 @@ +io.seata.saga.rm.SagaResourceManager \ No newline at end of file diff --git a/compatible/src/test/java/io/seata/common/LockAndCallback.java b/compatible/src/test/java/io/seata/common/LockAndCallback.java new file mode 100644 index 00000000000..a679fe25bfc --- /dev/null +++ b/compatible/src/test/java/io/seata/common/LockAndCallback.java @@ -0,0 +1,97 @@ +/* + * 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; + + +import io.seata.saga.engine.AsyncCallback; +import io.seata.saga.proctrl.ProcessContext; +import io.seata.saga.statelang.domain.ExecutionStatus; +import io.seata.saga.statelang.domain.StateMachineInstance; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +public class LockAndCallback { + private final Lock lock; + private final Condition notFinished; + private final AsyncCallback callback; + private final static long DEFAULT_TIMEOUT = 60000; + private String result; + + public LockAndCallback() { + lock = new ReentrantLock(); + notFinished = lock.newCondition(); + callback = new AsyncCallback() { + @Override + public void onFinished(ProcessContext context, StateMachineInstance stateMachineInstance) { + result = "onFinished"; + try { + lock.lock(); + notFinished.signal(); + } finally { + lock.unlock(); + } + } + + @Override + public void onError(ProcessContext context, StateMachineInstance stateMachineInstance, Exception exp) { + result = "onError"; + try { + lock.lock(); + notFinished.signal(); + } finally { + lock.unlock(); + } + } + }; + } + public void waitingForFinish(StateMachineInstance inst) { + waitingForFinish(inst, DEFAULT_TIMEOUT); + } + + public void waitingForFinish(StateMachineInstance inst, long timeout) { + if (ExecutionStatus.RU.equals(inst.getStatus())) { + long start = System.nanoTime(); + try { + lock.lock(); + boolean finished = notFinished.await(timeout, TimeUnit.MILLISECONDS); + if (finished) { + System.out.printf("finish wait ====== XID: %s, status: %s, compensationStatus: %s, cost: %d ms, result: %s\r\n", + inst.getId(), inst.getStatus(), inst.getCompensationStatus(), (System.nanoTime() - start) / 1000_000, result); + } else { + System.out.printf("timeout wait ====== XID: %s, status: %s, compensationStatus: %s, cost: %d ms, result: %s\r\n", + inst.getId(), inst.getStatus(), inst.getCompensationStatus(), (System.nanoTime() - start) / 1000_000, result); + } + } catch (Exception e) { + System.out.printf("error wait ====== XID: %s, status: %s, compensationStatus: %s, cost: %d ms, result: %s, error: %s\r\n", + inst.getId(), inst.getStatus(), inst.getCompensationStatus(), (System.nanoTime() - start) / 1000_000, result, e.getMessage()); + throw new RuntimeException("waitingForFinish failed", e); + } finally { + lock.unlock(); + } + } else { + System.out.printf("do not wait ====== XID: %s, status: %s, compensationStatus: %s, result: %s\r\n", + inst.getId(), inst.getStatus(), inst.getCompensationStatus(), result); + } + } + + public AsyncCallback getCallback() { + return callback; + } +} diff --git a/compatible/src/test/java/io/seata/integration/tx/api/interceptor/parser/GlobalTransactionalInterceptorParserTest.java b/compatible/src/test/java/io/seata/integration/tx/api/interceptor/parser/GlobalTransactionalInterceptorParserTest.java index f390888887f..7ea7712c741 100644 --- a/compatible/src/test/java/io/seata/integration/tx/api/interceptor/parser/GlobalTransactionalInterceptorParserTest.java +++ b/compatible/src/test/java/io/seata/integration/tx/api/interceptor/parser/GlobalTransactionalInterceptorParserTest.java @@ -20,7 +20,7 @@ import io.seata.tm.api.DefaultFailureHandlerImpl; import io.seata.tm.api.FailureHandler; import org.apache.seata.integration.tx.api.interceptor.handler.ProxyInvocationHandler; -import io.seata.integration.tx.api.util.ProxyUtil; +import org.apache.seata.integration.tx.api.util.ProxyUtil; import org.apache.seata.tm.api.FailureHandlerHolder; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; diff --git a/compatible/src/test/java/io/seata/rm/tcc/Param.java b/compatible/src/test/java/io/seata/rm/tcc/Param.java index 65af4f832a8..d6055e54e37 100644 --- a/compatible/src/test/java/io/seata/rm/tcc/Param.java +++ b/compatible/src/test/java/io/seata/rm/tcc/Param.java @@ -29,4 +29,4 @@ @Target({ElementType.PARAMETER, ElementType.FIELD}) public @interface Param { String value() default ""; -} +} \ No newline at end of file diff --git a/compatible/src/test/java/io/seata/rm/tcc/TccAction.java b/compatible/src/test/java/io/seata/rm/tcc/TccAction.java index 2f2037ebe08..c1a082f25e6 100644 --- a/compatible/src/test/java/io/seata/rm/tcc/TccAction.java +++ b/compatible/src/test/java/io/seata/rm/tcc/TccAction.java @@ -50,8 +50,7 @@ public interface TccAction { * @param actionContext the action context * @return the boolean */ - boolean commitWithArg(BusinessActionContext actionContext, @BusinessActionContextParameter("tccParam") - TccParam param, @Param("a") Integer a); + boolean commitWithArg(BusinessActionContext actionContext, @BusinessActionContextParameter("tccParam") TccParam param, @Param("a") Integer a); /** * Rollback boolean. @@ -61,7 +60,6 @@ boolean commitWithArg(BusinessActionContext actionContext, @BusinessActionContex */ boolean rollback(BusinessActionContext actionContext); - boolean rollbackWithArg(BusinessActionContext actionContext, @BusinessActionContextParameter("tccParam") - TccParam param); + boolean rollbackWithArg(BusinessActionContext actionContext, @BusinessActionContextParameter("tccParam") TccParam param); } diff --git a/compatible/src/test/java/io/seata/rm/tcc/interceptor/ProxyUtilsTccTest.java b/compatible/src/test/java/io/seata/rm/tcc/interceptor/ProxyUtilsTccTest.java index 2c8cadb74ff..b2ebb77c95d 100644 --- a/compatible/src/test/java/io/seata/rm/tcc/interceptor/ProxyUtilsTccTest.java +++ b/compatible/src/test/java/io/seata/rm/tcc/interceptor/ProxyUtilsTccTest.java @@ -30,7 +30,7 @@ import org.apache.seata.core.model.GlobalStatus; import org.apache.seata.core.model.Resource; import org.apache.seata.core.model.ResourceManager; -import io.seata.integration.tx.api.util.ProxyUtil; +import org.apache.seata.integration.tx.api.util.ProxyUtil; import org.apache.seata.rm.DefaultResourceManager; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; diff --git a/compatible/src/test/java/io/seata/rm/tcc/interceptor/parser/TccActionInterceptorParserTest.java b/compatible/src/test/java/io/seata/rm/tcc/interceptor/parser/TccActionInterceptorParserTest.java index ff2607cca6c..2bfcdbe8776 100644 --- a/compatible/src/test/java/io/seata/rm/tcc/interceptor/parser/TccActionInterceptorParserTest.java +++ b/compatible/src/test/java/io/seata/rm/tcc/interceptor/parser/TccActionInterceptorParserTest.java @@ -39,7 +39,7 @@ import org.apache.seata.core.model.TransactionManager; import org.apache.seata.integration.tx.api.interceptor.handler.ProxyInvocationHandler; import org.apache.seata.integration.tx.api.interceptor.parser.DefaultInterfaceParser; -import io.seata.integration.tx.api.util.ProxyUtil; +import org.apache.seata.integration.tx.api.util.ProxyUtil; import org.apache.seata.rm.DefaultResourceManager; import org.apache.seata.rm.tcc.TCCResourceManager; import org.apache.seata.tm.TransactionManagerHolder; @@ -62,7 +62,7 @@ public static void init() throws IOException { } @BeforeEach - private void before() { + public void before() { resourceManager = new TCCResourceManager() { @Override diff --git a/compatible/src/test/java/io/seata/saga/SagaCostPrint.java b/compatible/src/test/java/io/seata/saga/SagaCostPrint.java new file mode 100644 index 00000000000..2ac38764f2f --- /dev/null +++ b/compatible/src/test/java/io/seata/saga/SagaCostPrint.java @@ -0,0 +1,64 @@ +/* + * 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.saga; + + +import io.seata.saga.statelang.domain.StateMachineInstance; + +/** + */ +public class SagaCostPrint { + + public static StateMachineInstance executeAndPrint(String flag, Executor execute) throws Exception { + long start = System.nanoTime(); + + StateMachineInstance inst = null; + Exception e = null; + try { + inst = execute.run(); + } catch (Exception ex) { + ex.printStackTrace(); + e = ex; + throw ex; + } finally { + long cost = (System.nanoTime() - start) / 1000_000; + System.out.printf("====== XID: %s , cost%s: %d ms , error: %s\r\n", + inst != null ? inst.getId() : null, + flag, + cost, + (e != null ? e.getMessage() : null)); + } + return inst; + } + + public static void executeAndPrint(String flag, Runnable runnable) throws Exception { + executeAndPrint(flag, () -> { + runnable.run(); + return null; + }); + } + + @FunctionalInterface + public interface Executor { + StateMachineInstance run() throws Exception; + } + + @FunctionalInterface + public interface Runnable { + void run() throws Exception; + } +} diff --git a/compatible/src/test/java/io/seata/saga/engine/StateMachineTests.java b/compatible/src/test/java/io/seata/saga/engine/StateMachineTests.java new file mode 100644 index 00000000000..0be2dcea325 --- /dev/null +++ b/compatible/src/test/java/io/seata/saga/engine/StateMachineTests.java @@ -0,0 +1,315 @@ +/* + * 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.saga.engine; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import io.seata.saga.SagaCostPrint; +import io.seata.saga.engine.mock.DemoService.Engineer; +import io.seata.saga.engine.mock.DemoService.People; +import io.seata.saga.statelang.domain.DomainConstants; +import io.seata.saga.statelang.domain.ExecutionStatus; +import io.seata.saga.statelang.domain.StateMachineInstance; +import org.apache.seata.saga.statelang.parser.JsonParserFactory; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +/** + * State machine tests + * + */ +public class StateMachineTests { + + private static StateMachineEngine stateMachineEngine; + + @BeforeAll + public static void initApplicationContext() { + ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:saga/spring/statemachine_engine_test.xml"); + stateMachineEngine = applicationContext.getBean("stateMachineEngine", StateMachineEngine.class); + } + + @Test + public void testSimpleStateMachine() { + + stateMachineEngine.start("simpleTestStateMachine", null, new HashMap<>()); + } + + @Test + public void testSimpleStateMachineWithChoice() throws Exception { + String stateMachineName = "simpleChoiceTestStateMachine"; + + SagaCostPrint.executeAndPrint("1-1", () -> { + Map paramMap = new HashMap<>(); + paramMap.put("a", 1); + + stateMachineEngine.start(stateMachineName, null, paramMap); + }); + + SagaCostPrint.executeAndPrint("1-2", () -> { + Map paramMap = new HashMap<>(); + paramMap.put("a", 2); + + stateMachineEngine.start(stateMachineName, null, paramMap); + }); + } + + @Test + public void testSimpleStateMachineWithChoiceAndEnd() throws Exception { + String stateMachineName = "simpleChoiceAndEndTestStateMachine"; + + SagaCostPrint.executeAndPrint("1-3", () -> { + Map paramMap = new HashMap<>(1); + paramMap.put("a", 1); + + StateMachineInstance inst = stateMachineEngine.start(stateMachineName, null, paramMap); + }); + + SagaCostPrint.executeAndPrint("1-4", () -> { + Map paramMap = new HashMap<>(1); + paramMap.put("a", 3); + stateMachineEngine.start(stateMachineName, null, paramMap); + }); + } + + @Test + public void testSimpleInputAssignmentStateMachine() throws Exception { + String stateMachineName = "simpleInputAssignmentStateMachine"; + + SagaCostPrint.executeAndPrint("1-5", () -> { + Map paramMap = new HashMap<>(1); + paramMap.put("a", 1); + + StateMachineInstance inst = stateMachineEngine.start(stateMachineName, null, paramMap); + + String businessKey = inst.getStateList().get(0).getBusinessKey(); + Assertions.assertNotNull(businessKey); + System.out.println("====== businessKey :" + businessKey); + + String contextBusinessKey = (String)inst.getEndParams().get( + inst.getStateList().get(0).getName() + DomainConstants.VAR_NAME_BUSINESSKEY); + Assertions.assertNotNull(contextBusinessKey); + System.out.println("====== context businessKey :" + businessKey); + }); + } + + @Test + public void testSimpleCatchesStateMachine() throws Exception { + String stateMachineName = "simpleCachesStateMachine"; + + SagaCostPrint.executeAndPrint("1-6", () -> { + Map paramMap = new HashMap<>(1); + paramMap.put("a", 1); + paramMap.put("barThrowException", "true"); + + StateMachineInstance inst = stateMachineEngine.start(stateMachineName, null, paramMap); + + Assertions.assertNotNull(inst.getException()); + Assertions.assertEquals(ExecutionStatus.FA, inst.getStatus()); + }); + } + + @Test + public void testSimpleScriptTaskStateMachine() throws Exception { + String stateMachineName = "simpleScriptTaskStateMachine"; + + SagaCostPrint.executeAndPrint("1-7", () -> { + Map paramMap = new HashMap<>(1); + paramMap.put("a", 1); + + StateMachineInstance inst = stateMachineEngine.start(stateMachineName, null, paramMap); + + Assertions.assertEquals(ExecutionStatus.SU, inst.getStatus()); + Assertions.assertNotNull(inst.getEndParams().get("scriptStateResult")); + }); + + SagaCostPrint.executeAndPrint("1-8", () -> { + Map paramMap = new HashMap<>(1); + paramMap.put("a", 1); + + StateMachineInstance inst = stateMachineEngine.start(stateMachineName, null, paramMap); + + Assertions.assertEquals(ExecutionStatus.SU, inst.getStatus()); + }); + + SagaCostPrint.executeAndPrint("1-9", () -> { + Map paramMap = new HashMap<>(1); + paramMap.put("scriptThrowException", true); + + StateMachineInstance inst = stateMachineEngine.start(stateMachineName, null, paramMap); + + Assertions.assertEquals(ExecutionStatus.FA, inst.getStatus()); + }); + } + + @Test + public void testSimpleRetryStateMachine() throws Exception { + String stateMachineName = "simpleRetryStateMachine"; + + SagaCostPrint.executeAndPrint("1-10", () -> { + Map paramMap = new HashMap<>(1); + paramMap.put("a", 1); + paramMap.put("barThrowException", "true"); + + StateMachineInstance inst = stateMachineEngine.start(stateMachineName, null, paramMap); + + Assertions.assertNotNull(inst.getException()); + Assertions.assertEquals(ExecutionStatus.FA, inst.getStatus()); + }); + } + + @Test + public void testStatusMatchingStateMachine() throws Exception { + String stateMachineName = "simpleStatusMatchingStateMachine"; + + SagaCostPrint.executeAndPrint("1-11", () -> { + Map paramMap = new HashMap<>(2); + paramMap.put("a", 1); + paramMap.put("barThrowException", "true"); + + StateMachineInstance inst = stateMachineEngine.start(stateMachineName, null, paramMap); + + Assertions.assertNotNull(inst.getException()); + Assertions.assertEquals(ExecutionStatus.UN, inst.getStatus()); + }); + } + + @Test + public void testCompensationStateMachine() throws Exception { + String stateMachineName = "simpleCompensationStateMachine"; + + SagaCostPrint.executeAndPrint("1-12", () -> { + Map paramMap = new HashMap<>(1); + paramMap.put("a", 1); + paramMap.put("barThrowException", "true"); + + StateMachineInstance inst = stateMachineEngine.start(stateMachineName, null, paramMap); + + Assertions.assertEquals(ExecutionStatus.UN, inst.getStatus()); + Assertions.assertEquals(ExecutionStatus.SU, inst.getCompensationStatus()); + }); + } + + @Test + public void testCompensationAndSubStateMachine() throws Exception { + String stateMachineName = "simpleStateMachineWithCompensationAndSubMachine"; + + SagaCostPrint.executeAndPrint("1-13", () -> { + Map paramMap = new HashMap<>(1); + paramMap.put("a", 2); + paramMap.put("barThrowException", "true"); + + + StateMachineInstance inst = stateMachineEngine.start(stateMachineName, null, paramMap); + + Assertions.assertEquals(ExecutionStatus.UN, inst.getStatus()); + }); + } + + @Test + public void testCompensationAndSubStateMachineWithLayout() throws Exception { + String stateMachineName = "simpleStateMachineWithCompensationAndSubMachine_layout"; + + SagaCostPrint.executeAndPrint("1-14", () -> { + Map paramMap = new HashMap<>(1); + paramMap.put("a", 2); + paramMap.put("barThrowException", "true"); + + StateMachineInstance inst = stateMachineEngine.start(stateMachineName, null, paramMap); + + Assertions.assertEquals(ExecutionStatus.UN, inst.getStatus()); + }); + } + + @Test + public void testStateComplexParams() { + People people1 = new People(); + people1.setName("lilei"); + people1.setAge(18); + + People people2 = new People(); + people2.setName("lilei2"); + people2.setAge(19); + + People people3 = new People(); + people3.setName("lilei3"); + people3.setAge(20); + + People people4 = new People(); + people4.setName("lilei4"); + people4.setAge(21); + + people1.setChildrenArray(new People[]{people2}); + people1.setChildrenList(Collections.singletonList(people3)); + Map map1 = new HashMap<>(1); + map1.put("lilei4", people4); + people1.setChildrenMap(map1); + + String json = JsonParserFactory.getJsonParser("jackson").toJsonString(people1, false, true); + System.out.println(json); + } + + @Test + public void testStateMachineWithComplexParams() throws Exception { + String stateMachineName = "simpleStateMachineWithComplexParams"; + + SagaCostPrint.executeAndPrint("1-15", () -> { + Map paramMap = new HashMap<>(1); + People people = new People(); + people.setName("lilei"); + people.setAge(18); + + Engineer engineer = new Engineer(); + engineer.setName("programmer"); + + paramMap.put("people", people); + paramMap.put("career", engineer); + + StateMachineInstance inst = stateMachineEngine.start(stateMachineName, null, paramMap); + + People peopleResult = (People)inst.getEndParams().get("complexParameterMethodResult"); + Assertions.assertNotNull(peopleResult); + Assertions.assertEquals(people.getName(), peopleResult.getName()); + + Assertions.assertEquals(ExecutionStatus.SU, inst.getStatus()); + }); + } + + @Test + public void testSimpleStateMachineWithAsyncState() throws Exception { + String stateMachineName = "simpleStateMachineWithAsyncState"; + + SagaCostPrint.executeAndPrint("1-16", () -> { + Map paramMap = new HashMap<>(1); + paramMap.put("a", 1); + + StateMachineInstance inst = stateMachineEngine.start(stateMachineName, null, paramMap); + + Assertions.assertEquals(ExecutionStatus.SU, inst.getStatus()); + }); + + try { + Thread.sleep(500); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } +} diff --git a/compatible/src/test/java/io/seata/saga/engine/db/mockserver/StateMachineAsyncDBMockServerTests.java b/compatible/src/test/java/io/seata/saga/engine/db/mockserver/StateMachineAsyncDBMockServerTests.java new file mode 100644 index 00000000000..98daec128c2 --- /dev/null +++ b/compatible/src/test/java/io/seata/saga/engine/db/mockserver/StateMachineAsyncDBMockServerTests.java @@ -0,0 +1,202 @@ +/* + * 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.saga.engine.db.mockserver; + +import io.seata.common.LockAndCallback; +import io.seata.saga.SagaCostPrint; +import io.seata.saga.engine.StateMachineEngine; +import io.seata.saga.engine.mock.DemoService.People; +import io.seata.saga.rm.StateMachineEngineHolder; +import io.seata.saga.statelang.domain.ExecutionStatus; +import io.seata.saga.statelang.domain.StateMachineInstance; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +import java.util.HashMap; +import java.util.Map; + +/** + * State machine async tests with db log store + * + */ +public class StateMachineAsyncDBMockServerTests { + + private static StateMachineEngine stateMachineEngine; + + @BeforeAll + public static void initApplicationContext() throws InterruptedException { + ApplicationContext applicationContext = new ClassPathXmlApplicationContext( + "classpath:saga/spring/statemachine_engine_db_mockserver_test.xml"); + stateMachineEngine = applicationContext.getBean("stateMachineEngine", StateMachineEngine.class); + StateMachineEngineHolder.setStateMachineEngine(stateMachineEngine); + } + + @Test + public void testSimpleCatchesStateMachine() throws Exception { + String stateMachineName = "simpleCachesStateMachine"; + + SagaCostPrint.executeAndPrint("4-1", () -> { + Map paramMap = new HashMap<>(2); + paramMap.put("a", 1); + paramMap.put("barThrowException", "true"); + + LockAndCallback lockAndCallback = new LockAndCallback(); + StateMachineInstance inst = stateMachineEngine.startAsync(stateMachineName, null, paramMap, lockAndCallback.getCallback()); + lockAndCallback.waitingForFinish(inst); + + Assertions.assertNotNull(inst.getException()); + Assertions.assertEquals(ExecutionStatus.FA, inst.getStatus()); + }); + } + + @Test + public void testSimpleRetryStateMachine() throws Exception { + String stateMachineName = "simpleRetryStateMachine"; + + SagaCostPrint.executeAndPrint("4-2", () -> { + Map paramMap = new HashMap<>(2); + paramMap.put("a", 1); + paramMap.put("barThrowException", "true"); + + LockAndCallback lockAndCallback = new LockAndCallback(); + StateMachineInstance inst = stateMachineEngine.startAsync(stateMachineName, null, paramMap, lockAndCallback.getCallback()); + lockAndCallback.waitingForFinish(inst); + + Assertions.assertNotNull(inst.getException()); + Assertions.assertEquals(ExecutionStatus.FA, inst.getStatus()); + }); + } + + @Test + public void testStatusMatchingStateMachine() throws Exception { + String stateMachineName = "simpleStatusMatchingStateMachine"; + + SagaCostPrint.executeAndPrint("4-3", () -> { + Map paramMap = new HashMap<>(2); + paramMap.put("a", 1); + paramMap.put("barThrowException", "true"); + + LockAndCallback lockAndCallback = new LockAndCallback(); + StateMachineInstance inst = stateMachineEngine.startAsync(stateMachineName, null, paramMap, lockAndCallback.getCallback()); + lockAndCallback.waitingForFinish(inst); + + Assertions.assertNotNull(inst.getException()); + Assertions.assertEquals(ExecutionStatus.UN, inst.getStatus()); + }); + } + + @Test + public void testCompensationStateMachine() throws Exception { + String stateMachineName = "simpleCompensationStateMachine"; + + SagaCostPrint.executeAndPrint("4-4", () -> { + Map paramMap = new HashMap<>(1); + paramMap.put("a", 1); + paramMap.put("barThrowException", "true"); + + LockAndCallback lockAndCallback = new LockAndCallback(); + StateMachineInstance inst = stateMachineEngine.startAsync(stateMachineName, null, paramMap, lockAndCallback.getCallback()); + lockAndCallback.waitingForFinish(inst); + + Assertions.assertEquals(ExecutionStatus.UN, inst.getStatus()); + Assertions.assertEquals(ExecutionStatus.SU, inst.getCompensationStatus()); + }); + } + + @Test + public void testCompensationAndSubStateMachine() throws Exception { + String stateMachineName = "simpleStateMachineWithCompensationAndSubMachine"; + + SagaCostPrint.executeAndPrint("4-5", () -> { + Map paramMap = new HashMap<>(2); + paramMap.put("a", 2); + paramMap.put("barThrowException", "true"); + + LockAndCallback lockAndCallback = new LockAndCallback(); + StateMachineInstance inst = stateMachineEngine.startAsync(stateMachineName, null, paramMap, lockAndCallback.getCallback()); + lockAndCallback.waitingForFinish(inst); + + Assertions.assertEquals(ExecutionStatus.UN, inst.getStatus()); + }); + } + + @Test + public void testCompensationAndSubStateMachineWithLayout() throws Exception { + String stateMachineName = "simpleStateMachineWithCompensationAndSubMachine_layout"; + + SagaCostPrint.executeAndPrint("4-6", () -> { + Map paramMap = new HashMap<>(1); + paramMap.put("a", 2); + paramMap.put("barThrowException", "true"); + + LockAndCallback lockAndCallback = new LockAndCallback(); + StateMachineInstance inst = stateMachineEngine.startAsync(stateMachineName, null, paramMap, lockAndCallback.getCallback()); + lockAndCallback.waitingForFinish(inst); + + Assertions.assertEquals(ExecutionStatus.UN, inst.getStatus()); + }); + } + + @Test + public void testStateMachineWithComplexParams() throws Exception { + String stateMachineName = "simpleStateMachineWithComplexParamsJackson"; + + SagaCostPrint.executeAndPrint("4-7", () -> { + People people = new People(); + people.setName("lilei"); + people.setAge(18); + + Map paramMap = new HashMap<>(1); + paramMap.put("people", people); + + LockAndCallback lockAndCallback = new LockAndCallback(); + StateMachineInstance inst = stateMachineEngine.startAsync(stateMachineName, null, paramMap, lockAndCallback.getCallback()); + lockAndCallback.waitingForFinish(inst); + + People peopleResult = (People)inst.getEndParams().get("complexParameterMethodResult"); + Assertions.assertNotNull(peopleResult); + Assertions.assertEquals(people.getName(), peopleResult.getName()); + + Assertions.assertEquals(ExecutionStatus.SU, inst.getStatus()); + }); + } + + @Test + public void testSimpleStateMachineWithAsyncState() throws Exception { + String stateMachineName = "simpleStateMachineWithAsyncState"; + + SagaCostPrint.executeAndPrint("4-8", () -> { + Map paramMap = new HashMap<>(1); + paramMap.put("a", 1); + + LockAndCallback lockAndCallback = new LockAndCallback(); + StateMachineInstance inst = stateMachineEngine.startAsync(stateMachineName, null, paramMap, lockAndCallback.getCallback()); + lockAndCallback.waitingForFinish(inst); + + Assertions.assertEquals(ExecutionStatus.SU, inst.getStatus()); + }); + + try { + Thread.sleep(500); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } +} diff --git a/compatible/src/test/java/io/seata/saga/engine/db/mockserver/StateMachineDBMockServerTests.java b/compatible/src/test/java/io/seata/saga/engine/db/mockserver/StateMachineDBMockServerTests.java new file mode 100644 index 00000000000..f7647d4cda0 --- /dev/null +++ b/compatible/src/test/java/io/seata/saga/engine/db/mockserver/StateMachineDBMockServerTests.java @@ -0,0 +1,478 @@ +/* + * 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.saga.engine.db.mockserver; + +import io.seata.saga.SagaCostPrint; +import io.seata.saga.engine.StateMachineEngine; +import io.seata.saga.engine.mock.DemoService.Engineer; +import io.seata.saga.engine.mock.DemoService.People; +import io.seata.saga.rm.StateMachineEngineHolder; +import io.seata.saga.statelang.domain.DomainConstants; +import io.seata.saga.statelang.domain.ExecutionStatus; +import io.seata.saga.statelang.domain.StateMachineInstance; +import org.apache.seata.rm.DefaultResourceManager; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +import java.util.HashMap; +import java.util.Map; + +/** + * State machine tests with db log store + * + */ +public class StateMachineDBMockServerTests { + + private static StateMachineEngine stateMachineEngine; + + @BeforeAll + public static void initApplicationContext() { + ApplicationContext applicationContext = new ClassPathXmlApplicationContext( + "classpath:saga/spring/statemachine_engine_db_mockserver_test.xml"); + stateMachineEngine = applicationContext.getBean("stateMachineEngine", StateMachineEngine.class); + StateMachineEngineHolder.setStateMachineEngine(stateMachineEngine); + } + + @Test + public void testSimpleStateMachine() throws Exception { + SagaCostPrint.executeAndPrint("5-1", () -> { + stateMachineEngine.start("simpleTestStateMachine", null, new HashMap<>()); + }); + } + + @Test + public void testSimpleStateMachineWithChoice() throws Exception { + String stateMachineName = "simpleChoiceTestStateMachine"; + + SagaCostPrint.executeAndPrint("5-2", () -> { + Map paramMap = new HashMap<>(1); + paramMap.put("a", 1); + + String businessKey = String.valueOf(System.currentTimeMillis()); + StateMachineInstance inst = stateMachineEngine.startWithBusinessKey(stateMachineName, null, businessKey, paramMap); + + Assertions.assertNotNull(inst); + Assertions.assertEquals(ExecutionStatus.SU, inst.getStatus()); + + //TODO + inst = stateMachineEngine.getStateMachineConfig().getStateLogStore().getStateMachineInstanceByBusinessKey(businessKey, null); + Assertions.assertNotNull(inst); + Assertions.assertEquals(ExecutionStatus.SU, inst.getStatus()); + }); + + SagaCostPrint.executeAndPrint("5-3", () -> { + Map paramMap = new HashMap<>(1); + paramMap.put("a", 2); + + StateMachineInstance inst = stateMachineEngine.start(stateMachineName, null, paramMap); + + Assertions.assertNotNull(inst); + Assertions.assertEquals(ExecutionStatus.SU, inst.getStatus()); + }); + } + + @Test + public void testSimpleStateMachineWithChoiceAndEnd() throws Exception { + String stateMachineName = "simpleChoiceAndEndTestStateMachine"; + + SagaCostPrint.executeAndPrint("5-4", () -> { + Map paramMap = new HashMap<>(1); + paramMap.put("a", 1); + + StateMachineInstance inst = stateMachineEngine.start(stateMachineName, null, paramMap); + }); + + SagaCostPrint.executeAndPrint("5-5", () -> { + Map paramMap = new HashMap<>(1); + + paramMap.put("a", 3); + stateMachineEngine.start(stateMachineName, null, paramMap); + }); + } + + @Test + public void testSimpleInputAssignmentStateMachine() throws Exception { + String stateMachineName = "simpleInputAssignmentStateMachine"; + + SagaCostPrint.executeAndPrint("5-6", () -> { + Map paramMap = new HashMap<>(1); + paramMap.put("a", 1); + + StateMachineInstance inst = stateMachineEngine.start(stateMachineName, null, paramMap); + + String businessKey = inst.getStateList().get(0).getBusinessKey(); + Assertions.assertNotNull(businessKey); + System.out.println("====== businessKey :" + businessKey); + + String contextBusinessKey = (String)inst.getEndParams().get( + inst.getStateList().get(0).getName() + DomainConstants.VAR_NAME_BUSINESSKEY); + Assertions.assertNotNull(contextBusinessKey); + System.out.println("====== context businessKey :" + businessKey); + }); + } + + @Test + public void testSimpleCatchesStateMachine() throws Exception { + String stateMachineName = "simpleCachesStateMachine"; + + SagaCostPrint.executeAndPrint("5-7", () -> { + Map paramMap = new HashMap<>(2); + paramMap.put("a", 1); + paramMap.put("barThrowException", "true"); + + StateMachineInstance inst = stateMachineEngine.start(stateMachineName, null, paramMap); + + Assertions.assertNotNull(inst.getException()); + Assertions.assertEquals(ExecutionStatus.FA, inst.getStatus()); + }); + } + + @Test + public void testSimpleRetryStateMachine() throws Exception { + String stateMachineName = "simpleRetryStateMachine"; + + SagaCostPrint.executeAndPrint("5-11", () -> { + Map paramMap = new HashMap<>(2); + paramMap.put("a", 1); + paramMap.put("barThrowException", "true"); + + StateMachineInstance inst = stateMachineEngine.start(stateMachineName, null, paramMap); + + Assertions.assertNotNull(inst.getException()); + Assertions.assertEquals(ExecutionStatus.FA, inst.getStatus()); + }); + } + + @Test + public void testStatusMatchingStateMachine() throws Exception { + String stateMachineName = "simpleStatusMatchingStateMachine"; + + SagaCostPrint.executeAndPrint("5-12", () -> { + Map paramMap = new HashMap<>(2); + paramMap.put("a", 1); + paramMap.put("barThrowException", "true"); + + StateMachineInstance inst = stateMachineEngine.start(stateMachineName, null, paramMap); + + Assertions.assertNotNull(inst.getException()); + Assertions.assertEquals(ExecutionStatus.UN, inst.getStatus()); + }); + } + + @Test + public void testCompensationStateMachine() throws Exception { + String stateMachineName = "simpleCompensationStateMachine"; + + SagaCostPrint.executeAndPrint("5-13", () -> { + Map paramMap = new HashMap<>(2); + paramMap.put("a", 1); + paramMap.put("barThrowException", "true"); + + StateMachineInstance inst = stateMachineEngine.start(stateMachineName, null, paramMap); + + Assertions.assertEquals(ExecutionStatus.UN, inst.getStatus()); + Assertions.assertEquals(ExecutionStatus.SU, inst.getCompensationStatus()); + }); + } + + @Test + public void testSubStateMachine() throws Exception { + String stateMachineName = "simpleStateMachineWithCompensationAndSubMachine"; + + StateMachineInstance inst0 = SagaCostPrint.executeAndPrint("5-14", () -> { + Map paramMap = new HashMap<>(2); + paramMap.put("a", 2); + paramMap.put("barThrowException", "true"); + + StateMachineInstance inst = stateMachineEngine.start(stateMachineName, null, paramMap); + + Assertions.assertEquals(ExecutionStatus.UN, inst.getStatus()); + + return inst; + }); + + SagaCostPrint.executeAndPrint("5-15", () -> { + Map paramMap = new HashMap<>(2); + paramMap.put("a", 2); + paramMap.put("barThrowException", "false"); + + StateMachineInstance inst = stateMachineEngine.forward(inst0.getId(), paramMap); + + Assertions.assertEquals(ExecutionStatus.SU, inst.getStatus()); + }); + } + + @Test + public void testSubStateMachineWithLayout() throws Exception { + String stateMachineName = "simpleStateMachineWithCompensationAndSubMachine_layout"; + + StateMachineInstance inst0 = SagaCostPrint.executeAndPrint("5-16", () -> { + Map paramMap = new HashMap<>(2); + paramMap.put("a", 2); + paramMap.put("barThrowException", "true"); + + StateMachineInstance inst = stateMachineEngine.start(stateMachineName, null, paramMap); + + Assertions.assertEquals(ExecutionStatus.UN, inst.getStatus()); + + return inst; + }); + + SagaCostPrint.executeAndPrint("5-17", () -> { + Map paramMap = new HashMap<>(2); + paramMap.put("a", 2); + paramMap.put("barThrowException", "false"); + + StateMachineInstance inst = stateMachineEngine.forward(inst0.getId(), paramMap); + + Assertions.assertEquals(ExecutionStatus.SU, inst.getStatus()); + }); + } + + @Test + public void testForwardSubStateMachine() throws Exception { + String stateMachineName = "simpleStateMachineWithCompensationAndSubMachine"; + + StateMachineInstance inst0 = SagaCostPrint.executeAndPrint("5-18", () -> { + Map paramMap = new HashMap<>(2); + paramMap.put("a", 2); + paramMap.put("fooThrowException", "true"); + + StateMachineInstance inst = stateMachineEngine.start(stateMachineName, null, paramMap); + + Assertions.assertEquals(ExecutionStatus.UN, inst.getStatus()); + + return inst; + }); + + SagaCostPrint.executeAndPrint("5-19", () -> { + Map paramMap = new HashMap<>(2); + paramMap.put("a", 2); + paramMap.put("fooThrowException", "false"); + + StateMachineInstance inst = stateMachineEngine.forward(inst0.getId(), paramMap); + + Assertions.assertEquals(ExecutionStatus.SU, inst.getStatus()); + }); + } + + @Test + public void testUserDefCompensateSubStateMachine() throws Exception { + String stateMachineName = "simpleStateMachineWithUseDefCompensationSubMachine"; + + StateMachineInstance inst0 = SagaCostPrint.executeAndPrint("5-26", () -> { + Map paramMap = new HashMap<>(3); + paramMap.put("a", 2); + paramMap.put("barThrowException", "true"); + paramMap.put("compensateFooThrowException", "true"); + + StateMachineInstance inst = stateMachineEngine.start(stateMachineName, null, paramMap); + + Assertions.assertEquals(ExecutionStatus.UN, inst.getStatus()); + + return inst; + }); + + SagaCostPrint.executeAndPrint("5-27", () -> { + Map paramMap = new HashMap<>(3); + paramMap.put("a", 2); + paramMap.put("barThrowException", "true"); + paramMap.put("compensateFooThrowException", "false"); + + StateMachineInstance inst = stateMachineEngine.compensate(inst0.getId(), paramMap); + + Assertions.assertEquals(ExecutionStatus.SU, inst.getCompensationStatus()); + }); + } + + @Test + public void testCommitRetryingThenRetryCommitted() throws Exception { + String stateMachineName = "simpleCompensationStateMachineForRecovery"; + + StateMachineInstance inst0 = SagaCostPrint.executeAndPrint("5-28", () -> { + Map paramMap = new HashMap<>(2); + paramMap.put("a", 1); + paramMap.put("fooThrowException", "true"); + + StateMachineInstance inst = stateMachineEngine.start(stateMachineName, null, paramMap); + + Assertions.assertEquals(ExecutionStatus.UN, inst.getStatus()); + + return inst; + }); + + SagaCostPrint.executeAndPrint("5-29", () -> { + Map paramMap = new HashMap<>(2); + paramMap.put("a", 1); + paramMap.put("fooThrowException", "false"); + + StateMachineInstance inst = stateMachineEngine.forward(inst0.getId(), paramMap); + + Assertions.assertEquals(ExecutionStatus.SU, inst.getStatus()); + }); + } + + @Test + public void testCommitRetryingThenRetryRollbacked() throws Exception { + String stateMachineName = "simpleCompensationStateMachineForRecovery"; + + StateMachineInstance inst0 = SagaCostPrint.executeAndPrint("5-30", () -> { + Map paramMap = new HashMap<>(2); + paramMap.put("a", 1); + paramMap.put("fooThrowException", "true"); + + StateMachineInstance inst = stateMachineEngine.start(stateMachineName, null, paramMap); + + Assertions.assertEquals(ExecutionStatus.UN, inst.getStatus()); + + return inst; + }); + + SagaCostPrint.executeAndPrint("5-31", () -> { + Map paramMap = new HashMap<>(3); + paramMap.put("a", 1); + paramMap.put("fooThrowException", "false"); + paramMap.put("barThrowException", "true"); + + StateMachineInstance inst = stateMachineEngine.forward(inst0.getId(), paramMap); + + Assertions.assertEquals(ExecutionStatus.SU, inst.getCompensationStatus()); + }); + } + + @Test + public void testRollbackRetryingThenRetryRollbacked() throws Exception { + String stateMachineName = "simpleCompensationStateMachineForRecovery"; + + StateMachineInstance inst0 = SagaCostPrint.executeAndPrint("5-32", () -> { + Map paramMap = new HashMap<>(3); + paramMap.put("a", 1); + paramMap.put("barThrowException", "true"); + paramMap.put("compensateFooThrowException", "true"); + + StateMachineInstance inst = stateMachineEngine.start(stateMachineName, null, paramMap); + + Assertions.assertEquals(ExecutionStatus.UN, inst.getStatus()); + Assertions.assertEquals(ExecutionStatus.UN, inst.getCompensationStatus()); + + return inst; + }); + + SagaCostPrint.executeAndPrint("5-33", () -> { + Map paramMap = new HashMap<>(3); + paramMap.put("a", 1); + paramMap.put("barThrowException", "false"); + paramMap.put("compensateFooThrowException", "false"); + + StateMachineInstance inst = stateMachineEngine.compensate(inst0.getId(), paramMap); + + Assertions.assertEquals(ExecutionStatus.SU, inst.getCompensationStatus()); + }); + } + + @Test + public void testRollbackRetryingTwiceThenRetryRollbacked() throws Exception { + String stateMachineName = "simpleCompensationStateMachineForRecovery"; + + Map paramMap = new HashMap<>(3); + paramMap.put("a", 1); + paramMap.put("barThrowException", "true"); + paramMap.put("compensateFooThrowException", "true"); + + StateMachineInstance inst0 = SagaCostPrint.executeAndPrint("5-34", () -> { + StateMachineInstance inst = stateMachineEngine.start(stateMachineName, null, paramMap); + + Assertions.assertEquals(ExecutionStatus.UN, inst.getStatus()); + Assertions.assertEquals(ExecutionStatus.UN, inst.getCompensationStatus()); + + return inst; + }); + + SagaCostPrint.executeAndPrint("5-35", () -> { + StateMachineInstance inst = stateMachineEngine.compensate(inst0.getId(), paramMap); + + Assertions.assertEquals(ExecutionStatus.UN, inst.getStatus()); + Assertions.assertEquals(ExecutionStatus.UN, inst.getCompensationStatus()); + }); + + paramMap.put("barThrowException", "false"); + paramMap.put("compensateFooThrowException", "false"); + SagaCostPrint.executeAndPrint("5-36", () -> { + StateMachineInstance inst = stateMachineEngine.compensate(inst0.getId(), paramMap); + + Assertions.assertEquals(ExecutionStatus.SU, inst.getCompensationStatus()); + }); + } + + @Test + public void testStateMachineWithComplexParams() throws Exception { + String stateMachineName = "simpleStateMachineWithComplexParamsJackson"; + + SagaCostPrint.executeAndPrint("5-37", () -> { + People people = new People(); + people.setName("lilei"); + people.setAge(18); + + Engineer engineer = new Engineer(); + engineer.setName("programmer"); + + Map paramMap = new HashMap<>(2); + paramMap.put("people", people); + paramMap.put("career", engineer); + + StateMachineInstance instance = stateMachineEngine.start(stateMachineName, null, paramMap); + + People peopleResult = (People)instance.getEndParams().get("complexParameterMethodResult"); + Assertions.assertNotNull(peopleResult); + Assertions.assertEquals(people.getName(), peopleResult.getName()); + + Assertions.assertEquals(ExecutionStatus.SU, instance.getStatus()); + }); + } + + @Test + public void testSimpleStateMachineWithAsyncState() throws Exception { + String stateMachineName = "simpleStateMachineWithAsyncState"; + + SagaCostPrint.executeAndPrint("5-38", () -> { + Map paramMap = new HashMap<>(1); + paramMap.put("a", 1); + + StateMachineInstance inst = stateMachineEngine.start(stateMachineName, null, paramMap); + + Assertions.assertEquals(ExecutionStatus.SU, inst.getStatus()); + }); + + try { + Thread.sleep(500); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + @Test + public void testReloadStateMachineInstance() throws Exception { + SagaCostPrint.executeAndPrint("5-39", () -> { + StateMachineInstance instance = stateMachineEngine.getStateMachineConfig().getStateLogStore().getStateMachineInstance( + "10.15.232.93:8091:2019567124"); + System.out.println(instance); + }); + } +} diff --git a/compatible/src/test/java/io/seata/saga/engine/mock/DemoException.java b/compatible/src/test/java/io/seata/saga/engine/mock/DemoException.java new file mode 100644 index 00000000000..ea8866df109 --- /dev/null +++ b/compatible/src/test/java/io/seata/saga/engine/mock/DemoException.java @@ -0,0 +1,40 @@ +/* + * 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.saga.engine.mock; + + +public class DemoException extends RuntimeException { + + public DemoException() { + } + + public DemoException(String message) { + super(message); + } + + public DemoException(String message, Throwable cause) { + super(message, cause); + } + + public DemoException(Throwable cause) { + super(cause); + } + + public DemoException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/compatible/src/test/java/io/seata/saga/engine/mock/DemoService.java b/compatible/src/test/java/io/seata/saga/engine/mock/DemoService.java new file mode 100644 index 00000000000..1f3a618dd49 --- /dev/null +++ b/compatible/src/test/java/io/seata/saga/engine/mock/DemoService.java @@ -0,0 +1,187 @@ +/* + * 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.saga.engine.mock; + +import java.net.ConnectException; +import java.util.List; +import java.util.Map; + +/** + */ +public class DemoService { + + public Map foo(Map input) { + if(input == null){ + return null; + } + Integer sleepTime = (Integer) input.get("sleepTime"); + if(sleepTime != null){ + try { + Thread.sleep(sleepTime); + } catch (InterruptedException e) { + throw new DemoException(e); + } + } + if("true".equals(input.get("throwException"))){ + throw new DemoException("foo execute failed"); + } + if("true".equals(input.get("throwExceptionRandomly"))){ + if(Math.random() > 0.5){ + throw new DemoException("foo execute failed"); + } + } + return input; + } + + public Map compensateFoo(Map input) { + if(input == null){ + return null; + } + if("true".equals(input.get("throwException"))){ + throw new DemoException("compensateFoo execute failed"); + } + if("true".equals(input.get("throwExceptionRandomly"))){ + if(Math.random() > 0.8){ + throw new DemoException("compensateFoo execute failed"); + } + } + return input; + } + + public Map bar(Map input) { + if(input == null){ + return null; + } + Integer sleepTime = (Integer) input.get("sleepTime"); + if(sleepTime != null){ + try { + Thread.sleep(sleepTime); + } catch (InterruptedException e) { + throw new DemoException(e); + } + } + if("true".equals(input.get("throwException"))){ + throw new DemoException("bar execute failed"); + } + if("true".equals(input.get("throwExceptionRandomly"))){ + if(Math.random() > 0.5){ + throw new DemoException("bar execute failed"); + } + } + return input; + } + + public Map compensateBar(Map input) { + if(input == null){ + return null; + } + if("true".equals(input.get("throwException"))){ + throw new DemoException("compensateBar execute failed"); + } + if("true".equals(input.get("throwExceptionRandomly"))){ + if(Math.random() > 0.8){ + throw new DemoException("compensateBar execute failed"); + } + } + return input; + } + + public People complexParameterMethod(String name, int age, People people, People[] peopleArrya, List peopleList, Map peopleMap){ + return people; + } + + public Career interfaceParameterMethod(Career career){ + return career; + } + + public Map randomExceptionMethod(Map input) { + + double random = Math.random(); + if (random > 0.5) { + throw new DemoException("randomExceptionMethod execute failed"); + } + else { + throw new RuntimeException(new ConnectException("Connect Exception")); + } + } + + public static class People { + + private String name; + private int age; + + private People[] childrenArray; + private List childrenList; + private Map childrenMap; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + + public People[] getChildrenArray() { + return childrenArray; + } + + public void setChildrenArray(People[] childrenArray) { + this.childrenArray = childrenArray; + } + + public List getChildrenList() { + return childrenList; + } + + public void setChildrenList(List childrenList) { + this.childrenList = childrenList; + } + + public Map getChildrenMap() { + return childrenMap; + } + + public void setChildrenMap(Map childrenMap) { + this.childrenMap = childrenMap; + } + } + + public interface Career { + + } + + public static class Engineer implements Career { + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } +} diff --git a/compatible/src/test/java/io/seata/saga/engine/mock/MockGlobalTransaction.java b/compatible/src/test/java/io/seata/saga/engine/mock/MockGlobalTransaction.java new file mode 100644 index 00000000000..852cf599918 --- /dev/null +++ b/compatible/src/test/java/io/seata/saga/engine/mock/MockGlobalTransaction.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.saga.engine.mock; + +import org.apache.seata.core.context.RootContext; +import org.apache.seata.core.exception.TransactionException; +import org.apache.seata.core.model.GlobalStatus; +import org.apache.seata.saga.engine.sequence.UUIDSeqGenerator; +import org.apache.seata.tm.api.GlobalTransaction; +import org.apache.seata.tm.api.GlobalTransactionRole; +import org.apache.seata.tm.api.transaction.SuspendedResourcesHolder; + + +public class MockGlobalTransaction implements GlobalTransaction { + + private String xid; + private GlobalStatus status; + private long createTime; + + private static UUIDSeqGenerator uuidSeqGenerator = new UUIDSeqGenerator(); + + public MockGlobalTransaction() {} + + public MockGlobalTransaction(String xid) { + this.xid = xid; + } + + public MockGlobalTransaction(String xid, GlobalStatus status) { + this.xid = xid; + this.status = status; + } + + @Override + public void begin() throws TransactionException { + begin(60000); + } + + @Override + public void begin(int timeout) throws TransactionException { + this.createTime = System.currentTimeMillis(); + status = GlobalStatus.Begin; + xid = uuidSeqGenerator.generate(null); + RootContext.bind(xid); + } + + @Override + public void begin(int timeout, String name) throws TransactionException { + + } + + @Override + public void commit() throws TransactionException { + + } + + @Override + public void rollback() throws TransactionException { + + } + + @Override + public SuspendedResourcesHolder suspend() throws TransactionException { + return null; + } + + @Override + public SuspendedResourcesHolder suspend(boolean clean) + throws TransactionException { + return null; + } + + @Override + public void resume(SuspendedResourcesHolder suspendedResourcesHolder) + throws TransactionException { + + } + + @Override + public GlobalStatus getStatus() throws TransactionException { + return status; + } + + @Override + public String getXid() { + return xid; + } + + @Override + public void globalReport(GlobalStatus globalStatus) throws TransactionException { + + } + + @Override + public GlobalStatus getLocalStatus() { + return status; + } + + @Override + public GlobalTransactionRole getGlobalTransactionRole() { + return null; + } + + @Override + public long getCreateTime() { + return createTime; + } +} diff --git a/compatible/src/test/java/io/seata/saga/engine/mock/MockSagaTransactionTemplate.java b/compatible/src/test/java/io/seata/saga/engine/mock/MockSagaTransactionTemplate.java new file mode 100644 index 00000000000..3768a9b1383 --- /dev/null +++ b/compatible/src/test/java/io/seata/saga/engine/mock/MockSagaTransactionTemplate.java @@ -0,0 +1,82 @@ +/* + * 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.saga.engine.mock; + +import org.apache.seata.common.util.IdWorker; +import org.apache.seata.core.exception.TransactionException; +import org.apache.seata.core.model.BranchStatus; +import org.apache.seata.core.model.GlobalStatus; +import org.apache.seata.saga.engine.tm.SagaTransactionalTemplate; +import org.apache.seata.tm.api.GlobalTransaction; +import org.apache.seata.tm.api.TransactionalExecutor.ExecutionException; +import org.apache.seata.tm.api.transaction.TransactionInfo; + + +public class MockSagaTransactionTemplate implements SagaTransactionalTemplate { + + @Override + public void commitTransaction(GlobalTransaction tx) throws ExecutionException { + + } + + @Override + public void rollbackTransaction(GlobalTransaction tx, Throwable ex) throws TransactionException, ExecutionException { + + } + + @Override + public GlobalTransaction beginTransaction(TransactionInfo txInfo) throws ExecutionException { + GlobalTransaction globalTransaction = new MockGlobalTransaction(); + try { + globalTransaction.begin(); + } catch (TransactionException e) { + e.printStackTrace(); + } + return globalTransaction; + } + + @Override + public GlobalTransaction reloadTransaction(String xid) throws ExecutionException, TransactionException { + return new MockGlobalTransaction(xid, GlobalStatus.UnKnown); + } + + @Override + public void reportTransaction(GlobalTransaction tx, GlobalStatus globalStatus) throws ExecutionException { + + } + + @Override + public long branchRegister(String resourceId, String clientId, String xid, String applicationData, String lockKeys) + throws TransactionException { + return new IdWorker(null).nextId(); + } + + @Override + public void branchReport(String xid, long branchId, BranchStatus status, String applicationData) throws TransactionException { + + } + + @Override + public void triggerAfterCompletion(GlobalTransaction tx) { + + } + + @Override + public void cleanUp(GlobalTransaction tx) { + + } +} diff --git a/compatible/src/test/java/io/seata/saga/engine/mock/MockStateHandlerInterceptor.java b/compatible/src/test/java/io/seata/saga/engine/mock/MockStateHandlerInterceptor.java new file mode 100644 index 00000000000..df4085b0123 --- /dev/null +++ b/compatible/src/test/java/io/seata/saga/engine/mock/MockStateHandlerInterceptor.java @@ -0,0 +1,45 @@ +/* + * 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.saga.engine.mock; + +import org.apache.seata.saga.engine.exception.EngineExecutionException; +import org.apache.seata.saga.engine.pcext.InterceptableStateHandler; +import org.apache.seata.saga.engine.pcext.StateHandlerInterceptor; +import org.apache.seata.saga.proctrl.ProcessContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +public class MockStateHandlerInterceptor implements StateHandlerInterceptor { + + private static final Logger LOGGER = LoggerFactory.getLogger(MockStateHandlerInterceptor.class); + + @Override + public void preProcess(ProcessContext context) throws EngineExecutionException { + LOGGER.info("test StateHandlerInterceptor preProcess"); + } + + @Override + public void postProcess(ProcessContext context, Exception e) throws EngineExecutionException { + LOGGER.info("test StateHandlerInterceptor postProcess"); + } + + @Override + public boolean match(Class clazz) { + return true; + } +} diff --git a/compatible/src/test/java/io/seata/saga/engine/mock/MockStateRouterInterceptor.java b/compatible/src/test/java/io/seata/saga/engine/mock/MockStateRouterInterceptor.java new file mode 100644 index 00000000000..4c86f553c0b --- /dev/null +++ b/compatible/src/test/java/io/seata/saga/engine/mock/MockStateRouterInterceptor.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.saga.engine.mock; + +import org.apache.seata.saga.engine.exception.EngineExecutionException; +import org.apache.seata.saga.engine.pcext.InterceptableStateRouter; +import org.apache.seata.saga.engine.pcext.StateRouterInterceptor; +import org.apache.seata.saga.proctrl.Instruction; +import org.apache.seata.saga.proctrl.ProcessContext; +import org.apache.seata.saga.statelang.domain.State; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +public class MockStateRouterInterceptor implements StateRouterInterceptor { + + private static final Logger LOGGER = LoggerFactory.getLogger(MockStateRouterInterceptor.class); + + @Override + public void preRoute(ProcessContext context, State state) throws EngineExecutionException { + LOGGER.info("test StateRouterInterceptor preRoute"); + } + + @Override + public void postRoute(ProcessContext context, State state, Instruction instruction, Exception e) throws EngineExecutionException { + LOGGER.info("test StateRouterInterceptor postRoute"); + } + + @Override + public boolean match(Class clazz) { + return true; + } +} diff --git a/compatible/src/test/java/io/seata/spi/SPITest.java b/compatible/src/test/java/io/seata/spi/SPITest.java new file mode 100644 index 00000000000..7da08bd3ec3 --- /dev/null +++ b/compatible/src/test/java/io/seata/spi/SPITest.java @@ -0,0 +1,39 @@ +/* + * 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.spi; + +import org.apache.seata.common.loader.EnhancedServiceLoader; +import org.apache.seata.core.model.BranchType; +import org.apache.seata.core.model.ResourceManager; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.stream.Collectors; + +public class SPITest { + + @Test + public void testRmSPIOrder() { + EnhancedServiceLoader.unload(ResourceManager.class); + List resourceManagers = EnhancedServiceLoader.loadAll(ResourceManager.class); + List list = resourceManagers.stream().filter(resourceManager -> resourceManager.getBranchType().equals(BranchType.SAGA)).collect(Collectors.toList()); + Assertions.assertNotNull(list); + ResourceManager resourceManager = list.get(list.size() - 1); + Assertions.assertEquals("io.seata.saga.rm.SagaResourceManager", resourceManager.getClass().getName()); + } +} diff --git a/compatible/src/test/resources/file.conf b/compatible/src/test/resources/file.conf index 46c3e0401cc..01b00dffefd 100644 --- a/compatible/src/test/resources/file.conf +++ b/compatible/src/test/resources/file.conf @@ -22,4 +22,12 @@ service { default.grouplist = "127.0.0.1:8091" #disable seata disableGlobalTransaction = false -} \ No newline at end of file +} + +client { + rm { + sagaJsonParser = jackson + sagaRetryPersistModeUpdate = false + sagaCompensatePersistModeUpdate = false + } +} diff --git a/compatible/src/test/resources/saga/spring/statemachine_engine_db_mockserver_test.xml b/compatible/src/test/resources/saga/spring/statemachine_engine_db_mockserver_test.xml new file mode 100644 index 00000000000..a2b934c8fb0 --- /dev/null +++ b/compatible/src/test/resources/saga/spring/statemachine_engine_db_mockserver_test.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/compatible/src/test/resources/saga/spring/statemachine_engine_db_test.xml b/compatible/src/test/resources/saga/spring/statemachine_engine_db_test.xml new file mode 100644 index 00000000000..7371a92fdb8 --- /dev/null +++ b/compatible/src/test/resources/saga/spring/statemachine_engine_db_test.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/compatible/src/test/resources/saga/spring/statemachine_engine_test.xml b/compatible/src/test/resources/saga/spring/statemachine_engine_test.xml new file mode 100644 index 00000000000..b6f62d68737 --- /dev/null +++ b/compatible/src/test/resources/saga/spring/statemachine_engine_test.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/compatible/src/test/resources/saga/sql/db2_init.sql b/compatible/src/test/resources/saga/sql/db2_init.sql new file mode 100644 index 00000000000..1b2d913ebad --- /dev/null +++ b/compatible/src/test/resources/saga/sql/db2_init.sql @@ -0,0 +1,81 @@ +-- +-- 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. +-- + +create table seata_state_machine_def +( + id varchar(32) not null, + name varchar(128) not null, + tenant_id varchar(32) not null, + app_name varchar(32) not null, + type varchar(20), + comment_ varchar(255), + ver varchar(16) not null, + gmt_create timestamp(3) not null, + status varchar(2) not null, + content clob(65536) inline length 2048, + recover_strategy varchar(16), + primary key(id) +); + +create table seata_state_machine_inst +( + id varchar(128) not null, + machine_id varchar(32) not null, + tenant_id varchar(32) not null, + parent_id varchar(128), + gmt_started timestamp(3) not null, + business_key varchar(48), + uni_business_key varchar(128) not null generated always as( --Unique index does not allow empty columns on DB2 + CASE + WHEN "BUSINESS_KEY" IS NULL + THEN "ID" + ELSE "BUSINESS_KEY" + END), + start_params clob(65536) inline length 1024, + gmt_end timestamp(3), + excep blob(10240), + end_params clob(65536) inline length 1024, + status varchar(2), + compensation_status varchar(2), + is_running smallint, + gmt_updated timestamp(3) not null, + primary key(id) +); +create unique index state_machine_inst_unibuzkey on seata_state_machine_inst(uni_business_key, tenant_id); + +create table seata_state_inst +( + id varchar(48) not null, + machine_inst_id varchar(128) not null, + name varchar(128) not null, + type varchar(20), + service_name varchar(128), + service_method varchar(128), + service_type varchar(16), + business_key varchar(48), + state_id_compensated_for varchar(50), + state_id_retried_for varchar(50), + gmt_started timestamp(3) not null, + is_for_update smallint, + input_params clob(65536) inline length 1024, + output_params clob(65536) inline length 1024, + status varchar(2) not null, + excep blob(10240), + gmt_updated timestamp(3), + gmt_end timestamp(3), + primary key(id, machine_inst_id) +); \ No newline at end of file diff --git a/compatible/src/test/resources/saga/sql/h2_init.sql b/compatible/src/test/resources/saga/sql/h2_init.sql new file mode 100644 index 00000000000..742f62d464f --- /dev/null +++ b/compatible/src/test/resources/saga/sql/h2_init.sql @@ -0,0 +1,75 @@ +-- +-- 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. +-- + +create table if not exists seata_state_machine_def +( + id varchar(32) not null comment 'id', + name varchar(128) not null comment 'name', + tenant_id varchar(32) not null comment 'tenant id', + app_name varchar(32) not null comment 'application name', + type varchar(20) comment 'state language type', + comment_ varchar(255) comment 'comment', + ver varchar(16) not null comment 'version', + gmt_create timestamp(3) not null comment 'create time', + status varchar(2) not null comment 'status(AC:active|IN:inactive)', + content clob comment 'content', + recover_strategy varchar(16) comment 'transaction recover strategy(compensate|retry)', + primary key (id) +); + +create table if not exists seata_state_machine_inst +( + id varchar(128) not null comment 'id', + machine_id varchar(32) not null comment 'state machine definition id', + tenant_id varchar(32) not null comment 'tenant id', + parent_id varchar(128) comment 'parent id', + gmt_started timestamp(3) not null comment 'start time', + business_key varchar(48) comment 'business key', + start_params clob comment 'start parameters', + gmt_end timestamp(3) comment 'end time', + excep blob comment 'exception', + end_params clob comment 'end parameters', + status varchar(2) comment 'status(SU succeed|FA failed|UN unknown|SK skipped|RU running)', + compensation_status varchar(2) comment 'compensation status(SU succeed|FA failed|UN unknown|SK skipped|RU running)', + is_running tinyint(1) comment 'is running(0 no|1 yes)', + gmt_updated timestamp(3) not null, + primary key (id), + unique key unikey_buz_tenant (business_key, tenant_id) +); + +create table if not exists seata_state_inst +( + id varchar(48) not null comment 'id', + machine_inst_id varchar(128) not null comment 'state machine instance id', + name varchar(128) not null comment 'state name', + type varchar(20) comment 'state type', + service_name varchar(128) comment 'service name', + service_method varchar(128) comment 'method name', + service_type varchar(16) comment 'service type', + business_key varchar(48) comment 'business key', + state_id_compensated_for varchar(50) comment 'state compensated for', + state_id_retried_for varchar(50) comment 'state retried for', + gmt_started timestamp(3) not null comment 'start time', + is_for_update tinyint(1) comment 'is service for update', + input_params clob comment 'input parameters', + output_params clob comment 'output parameters', + status varchar(2) not null comment 'status(SU succeed|FA failed|UN unknown|SK skipped|RU running)', + excep blob comment 'exception', + gmt_updated timestamp(3) comment 'update time', + gmt_end timestamp(3) comment 'end time', + primary key (id, machine_inst_id) +); \ No newline at end of file diff --git a/compatible/src/test/resources/saga/sql/mysql_init.sql b/compatible/src/test/resources/saga/sql/mysql_init.sql new file mode 100644 index 00000000000..9a6ac51e928 --- /dev/null +++ b/compatible/src/test/resources/saga/sql/mysql_init.sql @@ -0,0 +1,81 @@ +-- +-- 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. +-- + +-- -------------------------------- The script used for sage -------------------------------- + + +CREATE TABLE IF NOT EXISTS `seata_state_machine_def` +( + `id` VARCHAR(32) NOT NULL COMMENT 'id', + `name` VARCHAR(128) NOT NULL COMMENT 'name', + `tenant_id` VARCHAR(32) NOT NULL COMMENT 'tenant id', + `app_name` VARCHAR(32) NOT NULL COMMENT 'application name', + `type` VARCHAR(20) COMMENT 'state language type', + `comment_` VARCHAR(255) COMMENT 'comment', + `ver` VARCHAR(16) NOT NULL COMMENT 'version', + `gmt_create` DATETIME(3) NOT NULL COMMENT 'create time', + `status` VARCHAR(2) NOT NULL COMMENT 'status(AC:active|IN:inactive)', + `content` TEXT COMMENT 'content', + `recover_strategy` VARCHAR(16) COMMENT 'transaction recover strategy(compensate|retry)', + PRIMARY KEY (`id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8; + +CREATE TABLE IF NOT EXISTS `seata_state_machine_inst` +( + `id` VARCHAR(128) NOT NULL COMMENT 'id', + `machine_id` VARCHAR(32) NOT NULL COMMENT 'state machine definition id', + `tenant_id` VARCHAR(32) NOT NULL COMMENT 'tenant id', + `parent_id` VARCHAR(128) COMMENT 'parent id', + `gmt_started` DATETIME(3) NOT NULL COMMENT 'start time', + `business_key` VARCHAR(48) COMMENT 'business key', + `start_params` TEXT COMMENT 'start parameters', + `gmt_end` DATETIME(3) COMMENT 'end time', + `excep` BLOB COMMENT 'exception', + `end_params` TEXT COMMENT 'end parameters', + `status` VARCHAR(2) COMMENT 'status(SU succeed|FA failed|UN unknown|SK skipped|RU running)', + `compensation_status` VARCHAR(2) COMMENT 'compensation status(SU succeed|FA failed|UN unknown|SK skipped|RU running)', + `is_running` TINYINT(1) COMMENT 'is running(0 no|1 yes)', + `gmt_updated` DATETIME(3) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `unikey_buz_tenant` (`business_key`, `tenant_id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8; + +CREATE TABLE IF NOT EXISTS `seata_state_inst` +( + `id` VARCHAR(48) NOT NULL COMMENT 'id', + `machine_inst_id` VARCHAR(128) NOT NULL COMMENT 'state machine instance id', + `name` VARCHAR(128) NOT NULL COMMENT 'state name', + `type` VARCHAR(20) COMMENT 'state type', + `service_name` VARCHAR(128) COMMENT 'service name', + `service_method` VARCHAR(128) COMMENT 'method name', + `service_type` VARCHAR(16) COMMENT 'service type', + `business_key` VARCHAR(48) COMMENT 'business key', + `state_id_compensated_for` VARCHAR(50) COMMENT 'state compensated for', + `state_id_retried_for` VARCHAR(50) COMMENT 'state retried for', + `gmt_started` DATETIME(3) NOT NULL COMMENT 'start time', + `is_for_update` TINYINT(1) COMMENT 'is service for update', + `input_params` TEXT COMMENT 'input parameters', + `output_params` TEXT COMMENT 'output parameters', + `status` VARCHAR(2) NOT NULL COMMENT 'status(SU succeed|FA failed|UN unknown|SK skipped|RU running)', + `excep` BLOB COMMENT 'exception', + `gmt_updated` DATETIME(3) COMMENT 'update time', + `gmt_end` DATETIME(3) COMMENT 'end time', + PRIMARY KEY (`id`, `machine_inst_id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8; \ No newline at end of file diff --git a/compatible/src/test/resources/saga/sql/oracle_init.sql b/compatible/src/test/resources/saga/sql/oracle_init.sql new file mode 100644 index 00000000000..db29e40c5f7 --- /dev/null +++ b/compatible/src/test/resources/saga/sql/oracle_init.sql @@ -0,0 +1,81 @@ +-- +-- 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. +-- + +CREATE TABLE seata_state_machine_def +( + id VARCHAR(32) NOT NULL, + name VARCHAR(128) NOT NULL, + tenant_id VARCHAR(32) NOT NULL, + app_name VARCHAR(32) NOT NULL, + type VARCHAR(20), + comment_ VARCHAR(255), + ver VARCHAR(16) NOT NULL, + gmt_create TIMESTAMP(3) NOT NULL, + status VARCHAR(2) NOT NULL, + content CLOB, + recover_strategy VARCHAR(16), + PRIMARY KEY (id) +); + +CREATE TABLE seata_state_machine_inst +( + id VARCHAR(128) NOT NULL, + machine_id VARCHAR(32) NOT NULL, + tenant_id VARCHAR(32) NOT NULL, + parent_id VARCHAR(128), + gmt_started TIMESTAMP(3) NOT NULL, + business_key VARCHAR(48), + uni_business_key VARCHAR(128) GENERATED ALWAYS AS ( + CASE + WHEN "BUSINESS_KEY" IS NULL + THEN "ID" + ELSE "BUSINESS_KEY" + END), + start_params CLOB, + gmt_end TIMESTAMP(3), + excep BLOB, + end_params CLOB, + status VARCHAR(2), + compensation_status VARCHAR(2), + is_running SMALLINT, + gmt_updated TIMESTAMP(3) NOT NULL, + PRIMARY KEY (id) +); +CREATE UNIQUE INDEX state_machine_inst_unibuzkey ON seata_state_machine_inst (uni_business_key, tenant_id); + +CREATE TABLE seata_state_inst +( + id VARCHAR(48) NOT NULL, + machine_inst_id VARCHAR(46) NOT NULL, + name VARCHAR(128) NOT NULL, + type VARCHAR(20), + service_name VARCHAR(128), + service_method VARCHAR(128), + service_type VARCHAR(16), + business_key VARCHAR(48), + state_id_compensated_for VARCHAR(50), + state_id_retried_for VARCHAR(50), + gmt_started TIMESTAMP(3) NOT NULL, + is_for_update SMALLINT, + input_params CLOB, + output_params CLOB, + status VARCHAR(2) NOT NULL, + excep BLOB, + gmt_updated TIMESTAMP(3), + gmt_end TIMESTAMP(3), + PRIMARY KEY (id, machine_inst_id) +); diff --git a/compatible/src/test/resources/saga/statelang/simple_statelang.json b/compatible/src/test/resources/saga/statelang/simple_statelang.json new file mode 100644 index 00000000000..14e52f4c6cc --- /dev/null +++ b/compatible/src/test/resources/saga/statelang/simple_statelang.json @@ -0,0 +1,19 @@ +{ + "Name": "simpleTestStateMachine", + "Comment": "测试状态机定义", + "StartState": "FirstState", + "Version": "0.0.2", + "States": { + "FirstState": { + "Type": "ServiceTask", + "ServiceName": "demoService", + "ServiceMethod": "foo", + "Next": "SecondState" + }, + "SecondState": { + "Type": "ServiceTask", + "ServiceName": "demoService", + "ServiceMethod": "bar" + } + } +} \ No newline at end of file diff --git a/compatible/src/test/resources/saga/statelang/simple_statelang_param_assignment.json b/compatible/src/test/resources/saga/statelang/simple_statelang_param_assignment.json new file mode 100644 index 00000000000..be7cef2a1a2 --- /dev/null +++ b/compatible/src/test/resources/saga/statelang/simple_statelang_param_assignment.json @@ -0,0 +1,74 @@ +{ + "Name": "simpleInputAssignmentStateMachine", + "Comment": "带输入输出参数赋值的测试状态机定义", + "StartState": "FirstState", + "Version": "0.0.1", + "States": { + "FirstState": { + "Type": "ServiceTask", + "ServiceName": "demoService", + "ServiceMethod": "foo", + "Next": "ChoiceState", + "Input": [ + { + "fooInput": "$.[a]", + "fooBusinessKey": "$Sequence.BUSINESS_KEY|SIMPLE" + } + ], + "Output": { + "fooResult": "$.#root" + } + }, + "ChoiceState":{ + "Type": "Choice", + "Choices":[ + { + "Expression":"[a] == 1", + "Next":"SecondState" + }, + { + "Expression":"[a] == 2", + "Next":"ThirdState" + } + ], + "Default":"Fail" + }, + "SecondState": { + "Type": "ServiceTask", + "ServiceName": "demoService", + "ServiceMethod": "bar", + "Input": [ + { + "barInput": "$.[fooResult]" + } + ], + "Output": { + "barResult": "$.#root" + }, + "Next": "Succeed" + }, + "ThirdState": { + "Type": "ServiceTask", + "ServiceName": "demoService", + "ServiceMethod": "foo", + "Input": [ + { + "fooInput": "[fooResult][a].list[0]" + } + ], + "Output": { + "fooResult": "$.#root" + }, + "listener":"", + "Next": "Succeed" + }, + "Succeed": { + "Type":"Succeed" + }, + "Fail": { + "Type":"Fail", + "ErrorCode": "NOT_FOUND", + "Message": "not found" + } + } +} \ No newline at end of file diff --git a/compatible/src/test/resources/saga/statelang/simple_statelang_with_async_state.json b/compatible/src/test/resources/saga/statelang/simple_statelang_with_async_state.json new file mode 100644 index 00000000000..f2bc3dd41aa --- /dev/null +++ b/compatible/src/test/resources/saga/statelang/simple_statelang_with_async_state.json @@ -0,0 +1,50 @@ +{ + "Name": "simpleStateMachineWithAsyncState", + "Comment": "带异步执行节点的测试状态机定义", + "StartState": "FirstState", + "Version": "0.0.1", + "States": { + "FirstState": { + "Type": "ServiceTask", + "ServiceName": "demoService", + "ServiceMethod": "foo", + "Next": "ChoiceState" + }, + "ChoiceState":{ + "Type": "Choice", + "Choices":[ + { + "Expression":"[a] == 1", + "Next":"SecondState" + }, + { + "Expression":"[a] == 2", + "Next":"ThirdState" + } + ], + "Default":"Fail" + }, + "SecondState": { + "Type": "ServiceTask", + "ServiceName": "demoService", + "ServiceMethod": "bar", + "IsAsync": true, + "Next": "Succeed" + }, + "ThirdState": { + "Type": "ServiceTask", + "ServiceName": "demoService", + "ServiceMethod": "foo", + "IsAsync": true, + "Next": "Succeed" + }, + "Succeed": { + "Type":"Succeed" + }, + "Fail": { + "Type":"Fail", + "ErrorCode": "NOT_FOUND", + "Message": "not found" + } + } +} \ No newline at end of file diff --git a/compatible/src/test/resources/saga/statelang/simple_statelang_with_catches.json b/compatible/src/test/resources/saga/statelang/simple_statelang_with_catches.json new file mode 100644 index 00000000000..d86f4fd3fe5 --- /dev/null +++ b/compatible/src/test/resources/saga/statelang/simple_statelang_with_catches.json @@ -0,0 +1,81 @@ +{ + "Name": "simpleCachesStateMachine", + "Comment": "带Caches异常路由的测试状态机定义", + "StartState": "FirstState", + "Version": "0.0.1", + "States": { + "FirstState": { + "Type": "ServiceTask", + "ServiceName": "demoService", + "ServiceMethod": "foo", + "Next": "ChoiceState", + "Input": [ + { + "fooInput": "$.[a]" + } + ], + "Output": { + "fooResult": "$.#root" + } + }, + "ChoiceState":{ + "Type": "Choice", + "Choices":[ + { + "Expression":"[a] == 1", + "Next":"SecondState" + }, + { + "Expression":"[a] == 2", + "Next":"ThirdState" + } + ], + "Default":"Fail" + }, + "SecondState": { + "Type": "ServiceTask", + "ServiceName": "demoService", + "ServiceMethod": "bar", + "Input": [ + { + "barInput": "$.[fooResult]", + "throwException": "$.[barThrowException]" + } + ], + "Output": { + "barResult": "$.#root" + }, + "Catch": [ + { + "Exceptions": [ + "io.seata.saga.engine.mock.DemoException" + ], + "Next": "Fail" + } + ], + "Next": "Succeed" + }, + "ThirdState": { + "Type": "ServiceTask", + "ServiceName": "demoService", + "ServiceMethod": "foo", + "Input": [ + { + "fooInput": "$.[fooResult]" + } + ], + "Output": { + "fooResult": "$.#root" + }, + "Next": "Succeed" + }, + "Succeed": { + "Type":"Succeed" + }, + "Fail": { + "Type":"Fail", + "ErrorCode": "NOT_FOUND", + "Message": "not found" + } + } +} diff --git a/compatible/src/test/resources/saga/statelang/simple_statelang_with_choice.json b/compatible/src/test/resources/saga/statelang/simple_statelang_with_choice.json new file mode 100644 index 00000000000..4be7a48f848 --- /dev/null +++ b/compatible/src/test/resources/saga/statelang/simple_statelang_with_choice.json @@ -0,0 +1,38 @@ +{ + "Name": "simpleChoiceTestStateMachine", + "Comment": "带条件分支的测试状态机定义", + "StartState": "FirstState", + "Version": "0.0.1", + "States": { + "FirstState": { + "Type": "ServiceTask", + "ServiceName": "demoService", + "ServiceMethod": "foo", + "Next": "ChoiceState" + }, + "ChoiceState":{ + "Type": "Choice", + "Choices":[ + { + "Expression":"[a] == 1", + "Next":"SecondState" + }, + { + "Expression":"[a] == 2", + "Next":"ThirdState" + } + ], + "Default":"SecondState" + }, + "SecondState": { + "Type": "ServiceTask", + "ServiceName": "demoService", + "ServiceMethod": "bar" + }, + "ThirdState": { + "Type": "ServiceTask", + "ServiceName": "demoService", + "ServiceMethod": "foo" + } + } +} \ No newline at end of file diff --git a/compatible/src/test/resources/saga/statelang/simple_statelang_with_choice_and_end.json b/compatible/src/test/resources/saga/statelang/simple_statelang_with_choice_and_end.json new file mode 100644 index 00000000000..7910b5332cb --- /dev/null +++ b/compatible/src/test/resources/saga/statelang/simple_statelang_with_choice_and_end.json @@ -0,0 +1,48 @@ +{ + "Name": "simpleChoiceAndEndTestStateMachine", + "Comment": "带条件分支和结束状态的测试状态机定义", + "StartState": "FirstState", + "Version": "0.0.1", + "States": { + "FirstState": { + "Type": "ServiceTask", + "ServiceName": "demoService", + "ServiceMethod": "foo", + "Next": "ChoiceState" + }, + "ChoiceState":{ + "Type": "Choice", + "Choices":[ + { + "Expression":"[a] == 1", + "Next":"SecondState" + }, + { + "Expression":"[a] == 2", + "Next":"ThirdState" + } + ], + "Default":"Fail" + }, + "SecondState": { + "Type": "ServiceTask", + "ServiceName": "demoService", + "ServiceMethod": "bar", + "Next": "Succeed" + }, + "ThirdState": { + "Type": "ServiceTask", + "ServiceName": "demoService", + "ServiceMethod": "foo", + "Next": "Succeed" + }, + "Succeed": { + "Type":"Succeed" + }, + "Fail": { + "Type":"Fail", + "ErrorCode": "NOT_FOUND", + "Message": "not found" + } + } +} \ No newline at end of file diff --git a/compatible/src/test/resources/saga/statelang/simple_statelang_with_choice_no_default.json b/compatible/src/test/resources/saga/statelang/simple_statelang_with_choice_no_default.json new file mode 100644 index 00000000000..808995bb3bc --- /dev/null +++ b/compatible/src/test/resources/saga/statelang/simple_statelang_with_choice_no_default.json @@ -0,0 +1,38 @@ +{ + "Name": "simpleChoiceNoDefaultTestStateMachine", + "Comment": "带条件分支但没有默认分支的测试状态机定义", + "StartState": "FirstState", + "Version": "0.0.1", + "States": { + "FirstState": { + "Type": "ServiceTask", + "ServiceName": "demoService", + "ServiceMethod": "foo", + "IsForUpdate": true, + "Next": "ChoiceState" + }, + "ChoiceState":{ + "Type": "Choice", + "Choices":[ + { + "Expression":"[a] == 1", + "Next":"SecondState" + }, + { + "Expression":"[a] == 2", + "Next":"ThirdState" + } + ] + }, + "SecondState": { + "Type": "ServiceTask", + "ServiceName": "demoService", + "ServiceMethod": "bar" + }, + "ThirdState": { + "Type": "ServiceTask", + "ServiceName": "demoService", + "ServiceMethod": "foo" + } + } +} \ No newline at end of file diff --git a/compatible/src/test/resources/saga/statelang/simple_statelang_with_compensation.json b/compatible/src/test/resources/saga/statelang/simple_statelang_with_compensation.json new file mode 100644 index 00000000000..a71e8a23a24 --- /dev/null +++ b/compatible/src/test/resources/saga/statelang/simple_statelang_with_compensation.json @@ -0,0 +1,130 @@ +{ + "Name": "simpleCompensationStateMachine", + "Comment": "带补偿定义的测试状态机定义", + "StartState": "FirstState", + "Version": "0.0.1", + "States": { + "FirstState": { + "Type": "ServiceTask", + "ServiceName": "demoService", + "ServiceMethod": "foo", + "CompensateState": "CompensateFirstState", + "Next": "ChoiceState", + "Input": [ + { + "fooInput": "$.[a]", + "throwException": "$.[fooThrowException]", + "sleepTime": "$.[fooSleepTime]" + } + ], + "Output": { + "fooResult": "$.#root" + }, + "Status": { + "$Exception{io.seata.saga.engine.mock.DemoException}": "UN", + "#root != null": "SU", + "#root == null": "FA" + }, + "Catch": [ + { + "Exceptions": [ + "io.seata.saga.engine.mock.DemoException" + ], + "Next": "CompensationTrigger" + } + ] + }, + "ChoiceState":{ + "Type": "Choice", + "Choices":[ + { + "Expression":"[a] == 1", + "Next":"SecondState" + }, + { + "Expression":"[a] == 2", + "Next":"ThirdState" + } + ], + "Default":"Fail" + }, + "SecondState": { + "Type": "ServiceTask", + "ServiceName": "demoService", + "ServiceMethod": "bar", + "CompensateState": "CompensateSecondState", + "Input": [ + { + "barInput": "$.[fooResult]", + "throwException": "$.[barThrowException]", + "sleepTime": "$.[barSleepTime]" + } + ], + "Output": { + "barResult": "$.#root" + }, + "Status": { + "$Exception{io.seata.saga.engine.mock.DemoException}": "UN", + "#root != null": "SU", + "#root == null": "FA" + }, + "Catch": [ + { + "Exceptions": [ + "io.seata.saga.engine.mock.DemoException" + ], + "Next": "CompensationTrigger" + } + ], + "Next": "Succeed" + }, + "ThirdState": { + "Type": "ServiceTask", + "ServiceName": "demoService", + "ServiceMethod": "foo", + "Input": [ + { + "fooInput": "$.[fooResult]" + } + ], + "Output": { + "fooResult": "$.#root" + }, + "Next": "Succeed" + }, + "CompensateFirstState": { + "Type": "ServiceTask", + "ServiceName": "demoService", + "ServiceMethod": "compensateFoo", + "Input": [ + { + "compensateFooInput": "$.[fooResult]", + "throwException": "$.[compensateFooThrowException]" + } + ] + }, + "CompensateSecondState": { + "Type": "ServiceTask", + "ServiceName": "demoService", + "ServiceMethod": "compensateBar", + "Input": [ + { + "compensateBarInput": "$.[barResult]", + "throwException": "$.[compensateBarThrowException]" + } + ] + }, + "CompensationTrigger": { + "Type": "CompensationTrigger", + "Next": "Fail" + }, + "Succeed": { + "Type":"Succeed" + }, + "Fail": { + "Type":"Fail", + "ErrorCode": "NOT_FOUND", + "Message": "not found" + } + } +} \ No newline at end of file diff --git a/compatible/src/test/resources/saga/statelang/simple_statelang_with_compensation_and_sub_machine.json b/compatible/src/test/resources/saga/statelang/simple_statelang_with_compensation_and_sub_machine.json new file mode 100644 index 00000000000..7dc4e60a935 --- /dev/null +++ b/compatible/src/test/resources/saga/statelang/simple_statelang_with_compensation_and_sub_machine.json @@ -0,0 +1,126 @@ +{ + "Name": "simpleStateMachineWithCompensationAndSubMachine", + "Comment": "带补偿定义和调用子状态机", + "StartState": "FirstState", + "Version": "0.0.1", + "IsRetryPersistModeUpdate": false, + "IsCompensatePersistModeUpdate": false, + "States": { + "FirstState": { + "Type": "ServiceTask", + "ServiceName": "demoService", + "ServiceMethod": "foo", + "CompensateState": "CompensateFirstState", + "Next": "ChoiceState", + "Input": [ + { + "fooInput": "$.[a]" + } + ], + "Output": { + "fooResult": "$.#root" + } + }, + "ChoiceState":{ + "Type": "Choice", + "Choices":[ + { + "Expression":"[a] == 1", + "Next":"SecondState" + }, + { + "Expression":"[a] == 2", + "Next":"CallSubStateMachine" + }, + { + "Expression": "[a] == 3 || [a] == 4", + "Next": "CallSubStateMachineAsUpdate" + } + ], + "Default":"Fail" + }, + "SecondState": { + "Type": "ServiceTask", + "ServiceName": "demoService", + "ServiceMethod": "bar", + "Input": [ + { + "barInput": "$.[fooResult]", + "throwException": "$.[barThrowException]" + } + ], + "Output": { + "barResult": "$.#root" + }, + "Status": { + "#root != null": "SU", + "#root == null": "FA", + "$Exception{io.seata.saga.engine.mock.DemoException}": "UN" + }, + "Catch": [ + { + "Exceptions": [ + "io.seata.saga.engine.mock.DemoException" + ], + "Next": "CompensationTrigger" + } + ], + "Next": "Succeed" + }, + "CallSubStateMachine": { + "Type": "SubStateMachine", + "StateMachineName": "simpleCompensationStateMachine", + "Input": [ + { + "a": "$.1", + "barThrowException": "$.[barThrowException]", + "fooThrowException": "$.[fooThrowException]", + "compensateFooThrowException": "$.[compensateFooThrowException]" + } + ], + "Output": { + "fooResult": "$.#root" + }, + "Next": "Succeed" + }, + "CallSubStateMachineAsUpdate": { + "Type": "SubStateMachine", + "StateMachineName": "simpleUpdateStateMachine", + "IsRetryPersistModeUpdate": true, + "IsCompensatePersistModeUpdate": true, + "Input": [ + { + "a": "$.[a]-2", + "barThrowException": "$.[barThrowException]", + "compensateBarThrowException": "$.[compensateBarThrowException]" + } + ], + "Output": { + "fooResult": "$.#root" + }, + "Next": "Succeed" + }, + "CompensateFirstState": { + "Type": "ServiceTask", + "ServiceName": "demoService", + "ServiceMethod": "compensateFoo", + "Input": [ + { + "compensateFooInput": "$.[fooResult]" + } + ] + }, + "CompensationTrigger": { + "Type": "CompensationTrigger", + "Next": "Fail" + }, + "Succeed": { + "Type":"Succeed" + }, + "Fail": { + "Type":"Fail", + "ErrorCode": "NOT_FOUND", + "Message": "not found" + } + } +} \ No newline at end of file diff --git a/compatible/src/test/resources/saga/statelang/simple_statelang_with_compensation_for_recovery.json b/compatible/src/test/resources/saga/statelang/simple_statelang_with_compensation_for_recovery.json new file mode 100644 index 00000000000..a8a7f2b2576 --- /dev/null +++ b/compatible/src/test/resources/saga/statelang/simple_statelang_with_compensation_for_recovery.json @@ -0,0 +1,139 @@ +{ + "Name": "simpleCompensationStateMachineForRecovery", + "Comment": "用于测试事务恢复的状态机定义", + "StartState": "FirstState", + "Version": "0.0.1", + "States": { + "FirstState": { + "Type": "ServiceTask", + "ServiceName": "demoService", + "ServiceMethod": "foo", + "CompensateState": "CompensateFirstState", + "Next": "ChoiceState", + "Input": [ + { + "fooInput": "$.[a]", + "throwExceptionRandomly": "$.[fooThrowExceptionRandomly]", + "throwException": "$.[fooThrowException]" + } + ], + "Output": { + "fooResult": "$.#root" + }, + "Status": { + "$Exception{io.seata.saga.engine.mock.DemoException}": "UN", + "#root != null && #root.size() > 0": "SU", + "#root == null || #root.size() == 0": "FA" + } + }, + "ChoiceState":{ + "Type": "Choice", + "Choices":[ + { + "Expression":"[a] == 1", + "Next":"SecondState" + }, + { + "Expression":"[a] == 2", + "Next":"ThirdState" + } + ], + "Default":"Fail" + }, + "SecondState": { + "Type": "ServiceTask", + "ServiceName": "demoService", + "ServiceMethod": "bar", + "CompensateState": "CompensateSecondState", + "Input": [ + { + "barInput": "$.[fooResult]", + "throwExceptionRandomly": "$.[barThrowExceptionRandomly]", + "throwException": "$.[barThrowException]" + } + ], + "Output": { + "barResult": "$.#root" + }, + "Status": { + "$Exception{io.seata.saga.engine.mock.DemoException}": "UN", + "#root != null && #root.size() > 0": "SU", + "#root == null || #root.size() == 0": "FA" + }, + "Catch": [ + { + "Exceptions": [ + "io.seata.saga.engine.mock.DemoException" + ], + "Next": "CompensationTrigger" + } + ], + "Next": "Succeed" + }, + "ThirdState": { + "Type": "ServiceTask", + "ServiceName": "demoService", + "ServiceMethod": "foo", + "Input": [ + { + "fooInput": "$.[fooResult]" + } + ], + "Output": { + "fooResult": "$.#root" + }, + "Status": { + "$Exception{io.seata.saga.engine.mock.DemoException}": "UN", + "#root != null && #root.size() > 0": "SU", + "#root == null || #root.size() == 0": "FA" + }, + "Next": "Succeed" + }, + "CompensateFirstState": { + "Type": "ServiceTask", + "ServiceName": "demoService", + "ServiceMethod": "compensateFoo", + "Input": [ + { + "compensateFooInput": "$.[a]", + "throwExceptionRandomly": "$.[compensateFooThrowExceptionRandomly]", + "throwException": "$.[compensateFooThrowException]" + } + ], + "Status": { + "$Exception{io.seata.saga.engine.mock.DemoException}": "UN", + "#root != null && #root.size() > 0": "SU", + "#root == null || #root.size() == 0": "FA" + } + }, + "CompensateSecondState": { + "Type": "ServiceTask", + "ServiceName": "demoService", + "ServiceMethod": "compensateBar", + "Input": [ + { + "compensateBarInput": "$.[a]", + "throwExceptionRandomly": "$.[compensateBarThrowExceptionRandomly]", + "throwException": "$.[compensateBarThrowException]" + } + ], + "Status": { + "$Exception{io.seata.saga.engine.mock.DemoException}": "UN", + "#root != null && #root.size() > 0": "SU", + "#root == null || #root.size() == 0": "FA" + } + }, + "CompensationTrigger": { + "Type": "CompensationTrigger", + "Next": "Fail" + }, + "Succeed": { + "Type":"Succeed" + }, + "Fail": { + "Type":"Fail", + "ErrorCode": "NOT_FOUND", + "Message": "not found" + } + } +} \ No newline at end of file diff --git a/compatible/src/test/resources/saga/statelang/simple_statelang_with_complex_params.json b/compatible/src/test/resources/saga/statelang/simple_statelang_with_complex_params.json new file mode 100644 index 00000000000..5c6f1cb47e1 --- /dev/null +++ b/compatible/src/test/resources/saga/statelang/simple_statelang_with_complex_params.json @@ -0,0 +1,119 @@ +{ + "Name": "simpleStateMachineWithComplexParams", + "Comment": "带复杂参数的测试状态机定义fastjson格式", + "StartState": "FirstState", + "Version": "0.0.1", + "States": { + "FirstState": { + "Type": "ServiceTask", + "ServiceName": "demoService", + "ServiceMethod": "complexParameterMethod", + "Next": "ChoiceState", + "ParameterTypes" : ["java.lang.String", "int", "io.seata.saga.engine.mock.DemoService$People", "[Lio.seata.saga.engine.mock.DemoService$People;", "java.util.List", "java.util.Map"], + "Input": [ + "$.[people].name", + "$.[people].age", + { + "name": "$.[people].name", + "age": "$.[people].age", + "childrenArray": [ + { + "name": "$.[people].name", + "age": "$.[people].age" + }, + { + "name": "$.[people].name", + "age": "$.[people].age" + } + ], + "childrenList": [ + { + "name": "$.[people].name", + "age": "$.[people].age" + }, + { + "name": "$.[people].name", + "age": "$.[people].age" + } + ], + "childrenMap": { + "lilei": { + "name": "$.[people].name", + "age": "$.[people].age" + } + } + }, + [ + { + "name": "$.[people].name", + "age": "$.[people].age" + }, + { + "name": "$.[people].name", + "age": "$.[people].age" + } + ], + [ + { + "@type": "io.seata.saga.engine.mock.DemoService$People", + "name": "$.[people].name", + "age": "$.[people].age" + } + ], + { + "lilei": { + "@type": "io.seata.saga.engine.mock.DemoService$People", + "name": "$.[people].name", + "age": "$.[people].age" + } + } + ], + "Output": { + "complexParameterMethodResult": "$.#root" + } + }, + "ChoiceState":{ + "Type": "Choice", + "Choices":[ + { + "Expression":"[complexParameterMethodResult].age > 0", + "Next":"SecondState" + }, + { + "Expression":"[complexParameterMethodResult].age <= 0", + "Next":"ThirdState" + } + ], + "Default":"Fail" + }, + "SecondState": { + "Type": "ServiceTask", + "ServiceName": "demoService", + "ServiceMethod": "interfaceParameterMethod", + "Input": [ + "$.[career]" + ], + "Output": { + "secondStateResult": "$.#root" + }, + "Next": "ThirdState" + }, + "ThirdState": { + "Type": "ServiceTask", + "ServiceName": "demoService", + "ServiceMethod": "interfaceParameterMethod", + "Input": [ + "$.[secondStateResult]" + ], + "Next": "Succeed" + }, + "Succeed": { + "Type":"Succeed" + }, + "Fail": { + "Type":"Fail", + "ErrorCode": "NOT_FOUND", + "Message": "not found" + } + } +} \ No newline at end of file diff --git a/compatible/src/test/resources/saga/statelang/simple_statelang_with_complex_params_jackson.json b/compatible/src/test/resources/saga/statelang/simple_statelang_with_complex_params_jackson.json new file mode 100644 index 00000000000..1ebb0006634 --- /dev/null +++ b/compatible/src/test/resources/saga/statelang/simple_statelang_with_complex_params_jackson.json @@ -0,0 +1,141 @@ +{ + "Name": "simpleStateMachineWithComplexParamsJackson", + "Comment": "带复杂参数的测试状态机定义jackson格式", + "StartState": "FirstState", + "Version": "0.0.1", + "States": { + "FirstState": { + "Type": "ServiceTask", + "ServiceName": "demoService", + "ServiceMethod": "complexParameterMethod", + "Next": "ChoiceState", + "ParameterTypes" : ["java.lang.String", "int", "io.seata.saga.engine.mock.DemoService$People", "[Lio.seata.saga.engine.mock.DemoService$People;", "java.util.List", "java.util.Map"], + "Input": [ + "$.[people].name", + "$.[people].age", + { + "@type": "io.seata.saga.engine.mock.DemoService$People", + "name": "lilei", + "age": 18, + "childrenArray": [ + "[Lio.seata.saga.engine.mock.DemoService$People;", + [ + { + "@type": "io.seata.saga.engine.mock.DemoService$People", + "name": "lilei", + "age": 18 + }, + { + "@type": "io.seata.saga.engine.mock.DemoService$People", + "name": "lilei", + "age": 18 + } + ] + ], + "childrenList": [ + "java.util.ArrayList", + [ + { + "@type": "io.seata.saga.engine.mock.DemoService$People", + "name": "lilei", + "age": 18 + }, + { + "@type": "io.seata.saga.engine.mock.DemoService$People", + "name": "lilei", + "age": 18 + } + ] + ], + "childrenMap": { + "@type": "java.util.LinkedHashMap", + "lilei": { + "@type": "io.seata.saga.engine.mock.DemoService$People", + "name": "lilei", + "age": 18 + } + } + }, + [ + "[Lio.seata.saga.engine.mock.DemoService$People;", + [ + { + "@type": "io.seata.saga.engine.mock.DemoService$People", + "name": "$.[people].name", + "age": "$.[people].age" + }, + { + "@type": "io.seata.saga.engine.mock.DemoService$People", + "name": "$.[people].name", + "age": "$.[people].age" + } + ] + ], + [ + "java.util.ArrayList", + [ + { + "@type": "io.seata.saga.engine.mock.DemoService$People", + "name": "$.[people].name", + "age": "$.[people].age" + } + ] + ], + { + "@type": "java.util.LinkedHashMap", + "lilei": { + "@type": "io.seata.saga.engine.mock.DemoService$People", + "name": "$.[people].name", + "age": "$.[people].age" + } + } + ], + "Output": { + "complexParameterMethodResult": "$.#root" + } + }, + "ChoiceState":{ + "Type": "Choice", + "Choices":[ + { + "Expression":"[complexParameterMethodResult].age > 0", + "Next":"SecondState" + }, + { + "Expression":"[complexParameterMethodResult].age <= 0", + "Next":"ThirdState" + } + ], + "Default":"Fail" + }, + "SecondState": { + "Type": "ServiceTask", + "ServiceName": "demoService", + "ServiceMethod": "interfaceParameterMethod", + "Input": [ + "$.[career]" + ], + "Output": { + "secondStateResult": "$.#root" + }, + "Next": "ThirdState" + }, + "ThirdState": { + "Type": "ServiceTask", + "ServiceName": "demoService", + "ServiceMethod": "interfaceParameterMethod", + "Input": [ + "$.[secondStateResult]" + ], + "Next": "Succeed" + }, + "Succeed": { + "Type":"Succeed" + }, + "Fail": { + "Type":"Fail", + "ErrorCode": "NOT_FOUND", + "Message": "not found" + } + } +} \ No newline at end of file diff --git a/compatible/src/test/resources/saga/statelang/simple_statelang_with_loop.json b/compatible/src/test/resources/saga/statelang/simple_statelang_with_loop.json new file mode 100644 index 00000000000..58ebaebaef3 --- /dev/null +++ b/compatible/src/test/resources/saga/statelang/simple_statelang_with_loop.json @@ -0,0 +1,137 @@ +{ + "Name": "simpleLoopTestStateMachine", + "Comment": "带循环参数的测试状态机定义", + "StartState": "FirstState", + "Version": "0.0.1", + "States": { + "FirstState": { + "Type": "ServiceTask", + "ServiceName": "demoService", + "ServiceMethod": "foo", + "CompensateState": "CompensateFirstState", + "Loop": { + "Parallel": 3, + "Collection": "$.[collection]", + "ElementVariableName": "element", + "ElementIndexName": "loopCounter", + "CompletionCondition": "[nrOfCompletedInstances] == ([collection].size()-4)" + }, + "Input": [ + { + "loopCounter": "$.[loopCounter]", + "element": "$.[element]", + "throwException": "$.[fooThrowException]" + } + ], + "Output": { + "fooResult": "$.#root" + }, + "Next": "ChoiceState" + }, + "ChoiceState":{ + "Type": "Choice", + "Choices":[ + { + "Expression": "[loopResult].?[#this[fooResult] == null].size() == 0 && [a] == 1", + "Next":"SecondState" + }, + { + "Expression": "[loopResult].?[#this[fooResult] == null].size() == 0 && [a] == 2", + "Next":"CallSubStateMachine" + } + ], + "Default":"Fail" + }, + "SecondState": { + "Type": "ServiceTask", + "ServiceName": "demoService", + "ServiceMethod": "bar", + "CompensateState": "CompensateSecondState", + "Loop": { + "Parallel": 3, + "Collection": "$.[collection]", + "ElementVariableName": "element", + "CompletionCondition": "[nrOfCompletedInstances] / [nrOfInstances] >= 0.4", + "ElementIndexName": "loopCounter" + }, + "Input": [ + { + "loopCounter": "$.[loopCounter]", + "loopElement": "$.[element]", + "throwException": "$.[barThrowException]" + } + ], + "Catch": [ + { + "Exceptions": [ + "io.seata.saga.engine.mock.DemoException" + ], + "Next": "CompensationTriggerTest" + } + ] + }, + "CallSubStateMachine": { + "Type": "SubStateMachine", + "StateMachineName": "simpleCompensationStateMachine", + "Loop": { + "Parallel": 3, + "Collection": "$.[collection]", + "ElementVariableName": "element", + "CompletionCondition": "[nrOfCompletedInstances] / [nrOfInstances] >= 0.4", + "ElementIndexName": "loopCounter" + }, + "Input": [ + { + "a": 1, + "collection": "$.[collection]", + "loopCounter": "$.[loopCounter]", + "element": "$.[element]", + "barThrowException": "$.[barThrowException]", + "fooThrowException": "$.[fooThrowException]", + "compensateFooThrowException": "$.[compensateFooThrowException]" + } + ], + "Output": { + "fooResult": "$.#root" + }, + "Next": "Succeed" + }, + "CompensateFirstState": { + "Type": "ServiceTask", + "ServiceName": "demoService", + "ServiceMethod": "compensateFoo", + "Input": [ + { + "compensateFooInput": "$.[fooResult]", + "throwException": "$.[compensateFooThrowException]", + "loopCounter": "$.[loopCounter]", + "element": "$.[element]" + } + ] + }, + "CompensateSecondState": { + "Type": "ServiceTask", + "ServiceName": "demoService", + "ServiceMethod": "compensateBar", + "Input": [ + { + "compensateBarInput": "$.[barResult]", + "loopCounter": "$.[loopCounter]", + "loopElement": "$.[element]" + } + ] + }, + "CompensationTriggerTest": { + "Type": "CompensationTrigger", + "Next": "Fail" + }, + "Succeed": { + "Type":"Succeed" + }, + "Fail": { + "Type":"Fail", + "ErrorCode": "NOT_FOUND", + "Message": "not found" + } + } +} \ No newline at end of file diff --git a/compatible/src/test/resources/saga/statelang/simple_statelang_with_persist_update_mode.json b/compatible/src/test/resources/saga/statelang/simple_statelang_with_persist_update_mode.json new file mode 100644 index 00000000000..873a301ccee --- /dev/null +++ b/compatible/src/test/resources/saga/statelang/simple_statelang_with_persist_update_mode.json @@ -0,0 +1,116 @@ +{ + "Name": "simpleUpdateStateMachine", + "Comment": "自定义中间状态是否持久化的测试状态机定义", + "StartState": "FirstState", + "Version": "0.0.1", + "IsRetryPersistModeUpdate": true, + "IsCompensatePersistModeUpdate": true, + "States": { + "FirstState": { + "Type": "ServiceTask", + "ServiceName": "demoService", + "ServiceMethod": "foo", + "Next": "ChoiceState", + "CompensateState": "CompensateFirstState", + "Input": [ + { + "fooInput": "$.[a]" + } + ], + "Output": { + "fooResult": "$.#root" + } + }, + "ChoiceState":{ + "Type": "Choice", + "Choices":[ + { + "Expression":"[a] == 1", + "Next":"SecondState" + }, + { + "Expression":"[a] == 2", + "Next":"ThirdState" + } + ], + "Default":"Fail" + }, + "SecondState": { + "Type": "ServiceTask", + "ServiceName": "demoService", + "ServiceMethod": "bar", + "Input": [ + { + "barInput": "$.[fooResult]", + "throwException": "$.[barThrowException]" + } + ], + "Output": { + "barResult": "$.#root" + }, + "Status": { + "$Exception{java.lang.Throwable}": "UN", + "#root != null": "SU", + "#root == null": "FA" + }, + "Next": "Succeed" + }, + "ThirdState": { + "Type": "ServiceTask", + "ServiceName": "demoService", + "ServiceMethod": "randomExceptionMethod", + "CompensateState": "CompensateThirdState", + "Input": [ + { + "barInput": "$.[fooResult]", + "throwException": "$.[barThrowException]" + } + ], + "Output": { + "barResult": "$.#root" + }, + "Status": { + "$Exception{java.lang.Throwable}": "UN", + "#root != null": "SU", + "#root == null": "FA" + }, + "Catch": [ + { + "Exceptions": [ + "java.lang.Throwable" + ], + "Next": "CompensationTrigger" + } + ], + "Next": "Succeed" + }, + "CompensateFirstState": { + "Type": "ServiceTask", + "ServiceName": "demoService", + "ServiceMethod": "compensateFoo" + }, + "CompensateThirdState": { + "Type": "ServiceTask", + "ServiceName": "demoService", + "ServiceMethod": "compensateBar", + "Input": [ + { + "compensateBarInput": "$.[barResult]", + "throwException": "$.[compensateBarThrowException]" + } + ] + }, + "CompensationTrigger": { + "Type": "CompensationTrigger", + "Next": "Fail" + }, + "Succeed": { + "Type":"Succeed" + }, + "Fail": { + "Type":"Fail", + "ErrorCode": "NOT_FOUND", + "Message": "not found" + } + } +} diff --git a/compatible/src/test/resources/saga/statelang/simple_statelang_with_recover_strategy.json b/compatible/src/test/resources/saga/statelang/simple_statelang_with_recover_strategy.json new file mode 100644 index 00000000000..1242d4595ea --- /dev/null +++ b/compatible/src/test/resources/saga/statelang/simple_statelang_with_recover_strategy.json @@ -0,0 +1,89 @@ +{ + "Name": "simpleStateMachineWithRecoverStrategy", + "Comment": "带自定义恢复策略的测试状态机定义", + "StartState": "FirstState", + "Version": "0.0.1", + "RecoverStrategy": "Forward", + "States": { + "FirstState": { + "Type": "ServiceTask", + "ServiceName": "demoService", + "ServiceMethod": "foo", + "Input": [ + { + "fooInput": "$.[a]", + "throwExceptionRandomly": "$.[fooThrowExceptionRandomly]", + "sleepTime": "$.[fooSleepTime]" + } + ], + "Output": { + "fooResult": "$.#root" + }, + "Status": { + "$Exception{io.seata.saga.engine.mock.DemoException}": "UN", + "#root != null": "SU", + "#root == null": "FA" + }, + "Catch": [ + { + "Exceptions": [ + "java.lang.Throwable" + ], + "Next": "Fail" + } + ], + "Next": "ChoiceState" + }, + "ChoiceState":{ + "Type": "Choice", + "Choices":[ + { + "Expression":"[a] == 1", + "Next":"SecondState" + }, + { + "Expression":"[a] == 2", + "Next":"Fail" + } + ], + "Default":"Fail" + }, + "SecondState": { + "Type": "ServiceTask", + "ServiceName": "demoService", + "ServiceMethod": "bar", + "Input": [ + { + "barInput": "$.[fooResult]", + "throwExceptionRandomly": "$.[barThrowExceptionRandomly]", + "sleepTime": "$.[barSleepTime]" + } + ], + "Output": { + "barResult": "$.#root" + }, + "Status": { + "$Exception{io.seata.saga.engine.mock.DemoException}": "UN", + "#root != null": "SU", + "#root == null": "FA" + }, + "Catch": [ + { + "Exceptions": [ + "java.lang.Throwable" + ], + "Next": "Fail" + } + ], + "Next": "Succeed" + }, + "Succeed": { + "Type":"Succeed" + }, + "Fail": { + "Type":"Fail", + "ErrorCode": "NOT_FOUND", + "Message": "not found" + } + } +} \ No newline at end of file diff --git a/compatible/src/test/resources/saga/statelang/simple_statelang_with_retry.json b/compatible/src/test/resources/saga/statelang/simple_statelang_with_retry.json new file mode 100644 index 00000000000..bcbb1cbbd47 --- /dev/null +++ b/compatible/src/test/resources/saga/statelang/simple_statelang_with_retry.json @@ -0,0 +1,94 @@ +{ + "Name": "simpleRetryStateMachine", + "Comment": "带异常重试的测试状态机定义", + "StartState": "FirstState", + "Version": "0.0.1", + "States": { + "FirstState": { + "Type": "ServiceTask", + "ServiceName": "demoService", + "ServiceMethod": "foo", + "Next": "ChoiceState", + "Input": [ + { + "fooInput": "$.[a]" + } + ], + "Output": { + "fooResult": "$.#root" + } + }, + "ChoiceState":{ + "Type": "Choice", + "Choices":[ + { + "Expression":"[a] == 1", + "Next":"SecondState" + }, + { + "Expression":"[a] == 2", + "Next":"ThirdState" + } + ], + "Default":"Fail" + }, + "SecondState": { + "Type": "ServiceTask", + "ServiceName": "demoService", + "ServiceMethod": "randomExceptionMethod", + "Input": [ + { + "barInput": "$.[fooResult]", + "throwException": "$.[barThrowException]" + } + ], + "Output": { + "barResult": "$.#root" + }, + "Retry": [ + { + "Exceptions": ["io.seata.saga.engine.mock.DemoException"], + "IntervalSeconds": 1.5, + "MaxAttempts": 3, + "BackoffRate": 1.5 + }, + { + "IntervalSeconds": 1, + "MaxAttempts": 3, + "BackoffRate": 1.5 + } + ], + "Catch": [ + { + "Exceptions": [ + "io.seata.saga.engine.mock.DemoException" + ], + "Next": "Fail" + } + ], + "Next": "Succeed" + }, + "ThirdState": { + "Type": "ServiceTask", + "ServiceName": "demoService", + "ServiceMethod": "foo", + "Input": [ + { + "fooInput": "$.[fooResult]" + } + ], + "Output": { + "fooResult": "$.#root" + }, + "Next": "Succeed" + }, + "Succeed": { + "Type":"Succeed" + }, + "Fail": { + "Type":"Fail", + "ErrorCode": "NOT_FOUND", + "Message": "not found" + } + } +} diff --git a/compatible/src/test/resources/saga/statelang/simple_statelang_with_script.json b/compatible/src/test/resources/saga/statelang/simple_statelang_with_script.json new file mode 100644 index 00000000000..c4acf156cb5 --- /dev/null +++ b/compatible/src/test/resources/saga/statelang/simple_statelang_with_script.json @@ -0,0 +1,104 @@ +{ + "Name": "simpleScriptTaskStateMachine", + "Comment": "带ScriptTask的测试状态机定义", + "StartState": "FirstState", + "Version": "0.0.1", + "States": { + "FirstState": { + "Type": "ServiceTask", + "ServiceName": "demoService", + "ServiceMethod": "foo", + "Next": "ScriptState", + "Input": [ + { + "fooInput": "$.[a]" + } + ], + "Output": { + "fooResult": "$.#root" + } + }, + "ScriptState": { + "Type": "ScriptTask", + "ScriptType": "groovy", + "ScriptContent": "if(throwException){ throw new RuntimeException(\"test\") } else { 'hello ' + inputA }", + "Input": [ + { + "inputA": "$.[a]", + "throwException": "$.[scriptThrowException]" + } + ], + "Output": { + "scriptStateResult": "$.#root" + }, + "Catch": [ + { + "Exceptions": [ + "java.lang.Throwable" + ], + "Next": "Fail" + } + ], + "Next": "ChoiceState" + }, + "ChoiceState":{ + "Type": "Choice", + "Choices":[ + { + "Expression":"[a] == 1", + "Next":"SecondState" + }, + { + "Expression":"[a] == 2", + "Next":"ThirdState" + } + ], + "Default":"Fail" + }, + "SecondState": { + "Type": "ServiceTask", + "ServiceName": "demoService", + "ServiceMethod": "bar", + "Input": [ + { + "barInput": "$.[fooResult]", + "throwException": "$.[barThrowException]" + } + ], + "Output": { + "barResult": "$.#root" + }, + "Catch": [ + { + "Exceptions": [ + "io.seata.saga.engine.mock.DemoException" + ], + "Next": "Fail" + } + ], + "Next": "Succeed" + }, + "ThirdState": { + "Type": "ServiceTask", + "ServiceName": "demoService", + "ServiceMethod": "foo", + "Input": [ + { + "fooInput": "$.[fooResult]" + } + ], + "Output": { + "fooResult": "$.#root" + }, + "Next": "Succeed" + }, + "Succeed": { + "Type":"Succeed" + }, + "Fail": { + "Type":"Fail", + "ErrorCode": "NOT_FOUND", + "Message": "not found" + } + } +} diff --git a/compatible/src/test/resources/saga/statelang/simple_statelang_with_status_matches.json b/compatible/src/test/resources/saga/statelang/simple_statelang_with_status_matches.json new file mode 100644 index 00000000000..7d58592d082 --- /dev/null +++ b/compatible/src/test/resources/saga/statelang/simple_statelang_with_status_matches.json @@ -0,0 +1,99 @@ +{ + "Name": "simpleStatusMatchingStateMachine", + "Comment": "带Task执行状态匹配的测试状态机定义", + "StartState": "FirstState", + "Version": "0.0.1", + "States": { + "FirstState": { + "Type": "ServiceTask", + "ServiceName": "demoService", + "ServiceMethod": "foo", + "Next": "ReturnNullState", + "Input": [ + { + "fooInput": "$.[a]" + } + ], + "Output": { + "fooResult": "$.#root" + } + }, + "ReturnNullState": { + "Type": "ServiceTask", + "ServiceName": "demoService", + "ServiceMethod": "foo", + "Next": "ChoiceState", + "Status": { + "$Exception{io.seata.saga.engine.mock.DemoException}": "UN", + "$Exception{java.lang.Exception}": "FA", + "#root != null && #root.size() > 0": "SU", + "#root == null || #root.size() == 0": "FA" + } + }, + "ChoiceState":{ + "Type": "Choice", + "Choices":[ + { + "Expression":"[a] == 1", + "Next":"SecondState" + }, + { + "Expression":"[a] == 2", + "Next":"ThirdState" + } + ], + "Default":"Fail" + }, + "SecondState": { + "Type": "ServiceTask", + "ServiceName": "demoService", + "ServiceMethod": "bar", + "Input": [ + { + "barInput": "$.[fooResult]", + "throwException": "$.[barThrowException]" + } + ], + "Output": { + "barResult": "$.#root" + }, + "Status": { + "$Exception{io.seata.saga.engine.mock.DemoException}": "UN", + "$Exception{java.lang.Exception}": "FA", + "#root != null && #root.size() > 0": "SU", + "#root == null || #root.size() == 0": "FA" + }, + "Catch": [ + { + "Exceptions": [ + "io.seata.saga.engine.mock.DemoException" + ], + "Next": "Fail" + } + ], + "Next": "Succeed" + }, + "ThirdState": { + "Type": "ServiceTask", + "ServiceName": "demoService", + "ServiceMethod": "foo", + "Input": [ + { + "fooInput": "$.[fooResult]" + } + ], + "Output": { + "fooResult": "$.#root" + }, + "Next": "Succeed" + }, + "Succeed": { + "Type":"Succeed" + }, + "Fail": { + "Type":"Fail", + "ErrorCode": "NOT_FOUND", + "Message": "not found" + } + } +} \ No newline at end of file diff --git a/compatible/src/test/resources/saga/statelang/simple_statelang_with_userdef_sub_machine_compensation.json b/compatible/src/test/resources/saga/statelang/simple_statelang_with_userdef_sub_machine_compensation.json new file mode 100644 index 00000000000..cefecb785b2 --- /dev/null +++ b/compatible/src/test/resources/saga/statelang/simple_statelang_with_userdef_sub_machine_compensation.json @@ -0,0 +1,118 @@ +{ + "Name": "simpleStateMachineWithUseDefCompensationSubMachine", + "Comment": "自定义补偿子状态机", + "StartState": "FirstState", + "Version": "0.0.1", + "States": { + "FirstState": { + "Type": "ServiceTask", + "ServiceName": "demoService", + "ServiceMethod": "foo", + "CompensateState": "CompensateFirstState", + "Next": "ChoiceState", + "Input": [ + { + "fooInput": "$.[a]" + } + ], + "Output": { + "fooResult": "$.#root" + }, + "Status": { + "#root != null": "SU", + "#root == null": "FA", + "$Exception{io.seata.saga.engine.mock.DemoException}": "UN" + } + }, + "ChoiceState":{ + "Type": "Choice", + "Choices":[ + { + "Expression":"[a] == 1", + "Next":"SecondState" + }, + { + "Expression":"[a] == 2", + "Next":"CallSubStateMachine" + } + ], + "Default":"Fail" + }, + "SecondState": { + "Type": "ServiceTask", + "ServiceName": "demoService", + "ServiceMethod": "bar", + "CompensateState": "CompensateSecondState", + "Input": [ + { + "barInput": "$.[fooResult]", + "throwException": "$.[barThrowException]" + } + ], + "Output": { + "barResult": "$.#root" + }, + "Status": { + "#root != null": "SU", + "#root == null": "FA", + "$Exception{io.seata.saga.engine.mock.DemoException}": "UN" + }, + "Catch": [ + { + "Exceptions": [ + "io.seata.saga.engine.mock.DemoException" + ], + "Next": "CompensationTrigger" + } + ], + "Next": "Succeed" + }, + "CallSubStateMachine": { + "Type": "SubStateMachine", + "StateMachineName": "simpleCompensationStateMachine", + "CompensateState": "CompensateSubMachine", + "Input": [ + { + "a": "$.1", + "barThrowException": "$.[barThrowException]", + "fooThrowException": "$.[fooThrowException]", + "compensateFooThrowException": "$.[compensateFooThrowException]" + } + ], + "Output": { + "fooResult": "$.#root" + }, + "Next": "Succeed" + }, + "CompensateFirstState": { + "Type": "ServiceTask", + "ServiceName": "demoService", + "ServiceMethod": "compensateFoo", + "Input": [ + { + "compensateFooInput": "$.[fooResult]" + } + ] + }, + "CompensateSubMachine": { + "Type": "CompensateSubMachine", + "Input": [ + { + "compensateFooThrowException": "$.[compensateFooThrowException]" + } + ] + }, + "CompensationTrigger": { + "Type": "CompensationTrigger", + "Next": "Fail" + }, + "Succeed": { + "Type":"Succeed" + }, + "Fail": { + "Type":"Fail", + "ErrorCode": "NOT_FOUND", + "Message": "not found" + } + } +} \ No newline at end of file diff --git a/compatible/src/test/resources/saga/statelang/simple_statemachine_with_layout.json b/compatible/src/test/resources/saga/statelang/simple_statemachine_with_layout.json new file mode 100644 index 00000000000..4d886a02c36 --- /dev/null +++ b/compatible/src/test/resources/saga/statelang/simple_statemachine_with_layout.json @@ -0,0 +1,288 @@ +{ + "nodes": [ + { + "type": "node", + "size": "72*72", + "shape": "flow-circle", + "color": "#FA8C16", + "label": "Start", + "stateId": "Start", + "stateType": "Start", + "stateProps": { + "StateMachine": { + "Name": "simpleStateMachineWithCompensationAndSubMachine_layout", + "Comment": "带补偿定义和调用子状态机", + "Version": "0.0.1" + } + }, + "x": 199.875, + "y": 95, + "id": "e2d86441" + }, + { + "type": "node", + "size": "110*48", + "shape": "flow-rect", + "color": "#1890FF", + "label": "FirstState", + "stateId": "FirstState", + "stateType": "ServiceTask", + "stateProps": { + "ServiceName": "demoService", + "ServiceMethod": "foo", + "Input": [ + { + "fooInput": "$.[a]" + } + ], + "Output": { + "fooResult": "$.#root" + } + }, + "x": 199.875, + "y": 213, + "id": "6111bf54" + }, + { + "type": "node", + "size": "80*72", + "shape": "flow-rhombus", + "color": "#13C2C2", + "label": "ChoiceState", + "stateId": "ChoiceState", + "stateType": "Choice", + "x": 199.875, + "y": 341.5, + "id": "5610fa37" + }, + { + "type": "node", + "size": "110*48", + "shape": "flow-rect", + "color": "#1890FF", + "label": "SecondState", + "stateId": "SecondState", + "stateType": "ServiceTask", + "stateProps": { + "ServiceName": "demoService", + "ServiceMethod": "bar", + "Input": [ + { + "barInput": "$.[fooResult]", + "throwException": "$.[barThrowException]" + } + ], + "Output": { + "barResult": "$.#root" + }, + "Status": { + "#root != null": "SU", + "#root == null": "FA", + "$Exception{io.seata.saga.engine.exception.EngineExecutionException}": "UN" + } + }, + "x": 199.375, + "y": 468, + "id": "af5591f9" + }, + { + "type": "node", + "size": "72*72", + "shape": "flow-circle", + "color": "#05A465", + "label": "Succeed", + "stateId": "Succeed", + "stateType": "Succeed", + "x": 199.375, + "y": 609, + "id": "2fd4c8de" + }, + { + "type": "node", + "size": "110*48", + "shape": "flow-rect", + "color": "#FA8C16", + "label": "SubStateMachine", + "stateId": "CallSubStateMachine", + "stateType": "SubStateMachine", + "stateProps": { + "StateMachineName": "simpleCompensationStateMachine", + "Input": [ + { + "a": "$.1", + "barThrowException": "$.[barThrowException]", + "fooThrowException": "$.[fooThrowException]", + "compensateFooThrowException": "$.[compensateFooThrowException]" + } + ], + "Output": { + "fooResult": "$.#root" + } + }, + "x": 55.875, + "y": 467, + "id": "04ea55a5" + }, + { + "type": "node", + "size": "110*48", + "shape": "flow-capsule", + "color": "#722ED1", + "label": "CompenFirstState", + "stateId": "CompensateFirstState", + "stateType": "Compensation", + "stateProps": { + "ServiceName": "demoService", + "ServiceMethod": "compensateFoo", + "Input": [ + { + "compensateFooInput": "$.[fooResult]" + } + ] + }, + "x": 68.875, + "y": 126, + "id": "6a09a5c2" + }, + { + "type": "node", + "size": "39*39", + "shape": "flow-circle", + "color": "red", + "label": "Catch", + "stateId": "Catch", + "stateType": "Catch", + "x": 257.875, + "y": 492, + "id": "e28af1c2" + }, + { + "type": "node", + "size": "110*48", + "shape": "flow-capsule", + "color": "red", + "label": "Compensation\nTrigger", + "stateId": "CompensationTrigger", + "stateType": "CompensationTrigger", + "x": 366.875, + "y": 491.5, + "id": "e32417a0" + }, + { + "type": "node", + "size": "72*72", + "shape": "flow-circle", + "color": "red", + "label": "Fail", + "stateId": "Fail", + "stateType": "Fail", + "stateProps": { + "ErrorCode": "NOT_FOUND", + "Message": "not found" + }, + "x": 513.375, + "y": 491.5, + "id": "d21d24c9" + } + ], + "edges": [ + { + "source": "e2d86441", + "sourceAnchor": 2, + "target": "6111bf54", + "targetAnchor": 0, + "id": "51f30b96" + }, + { + "source": "6111bf54", + "sourceAnchor": 2, + "target": "5610fa37", + "targetAnchor": 0, + "id": "8c3029b1" + }, + { + "source": "5610fa37", + "sourceAnchor": 2, + "target": "af5591f9", + "targetAnchor": 0, + "id": "a9e7d5b4", + "stateProps": { + "Expression": "[a] == 1", + "Default": false + }, + "label": "", + "shape": "flow-smooth" + }, + { + "source": "af5591f9", + "sourceAnchor": 2, + "target": "2fd4c8de", + "targetAnchor": 0, + "id": "61f34a49" + }, + { + "source": "6111bf54", + "sourceAnchor": 3, + "target": "6a09a5c2", + "targetAnchor": 2, + "id": "553384ab", + "style": { + "lineDash": "4" + } + }, + { + "source": "5610fa37", + "sourceAnchor": 3, + "target": "04ea55a5", + "targetAnchor": 0, + "id": "2ee91c33", + "stateProps": { + "Expression": "[a] == 2", + "Default": false + }, + "label": "", + "shape": "flow-smooth" + }, + { + "source": "e28af1c2", + "sourceAnchor": 1, + "target": "e32417a0", + "targetAnchor": 3, + "id": "d854a4d0", + "stateProps": { + "Exceptions": [ + "io.seata.common.exception.FrameworkException" + ] + }, + "label": "", + "shape": "flow-smooth" + }, + { + "source": "04ea55a5", + "sourceAnchor": 2, + "target": "2fd4c8de", + "targetAnchor": 3, + "id": "28734ad2" + }, + { + "source": "5610fa37", + "sourceAnchor": 1, + "target": "d21d24c9", + "targetAnchor": 0, + "id": "7c7595c0", + "stateProps": { + "Expression": "", + "Default": true + }, + "label": "", + "shape": "flow-smooth" + }, + { + "source": "e32417a0", + "sourceAnchor": 1, + "target": "d21d24c9", + "targetAnchor": 3, + "id": "16d809ce" + } + ] +} \ No newline at end of file diff --git a/seata-spring-boot-starter/src/main/java/org/apache/seata/spring/boot/autoconfigure/SeataSagaAutoConfiguration.java b/seata-spring-boot-starter/src/main/java/org/apache/seata/spring/boot/autoconfigure/SeataSagaAutoConfiguration.java index 43c18629fc3..ac3bf0d1ac8 100644 --- a/seata-spring-boot-starter/src/main/java/org/apache/seata/spring/boot/autoconfigure/SeataSagaAutoConfiguration.java +++ b/seata-spring-boot-starter/src/main/java/org/apache/seata/spring/boot/autoconfigure/SeataSagaAutoConfiguration.java @@ -16,11 +16,11 @@ */ package org.apache.seata.spring.boot.autoconfigure; +import javax.sql.DataSource; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.RejectedExecutionHandler; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; -import javax.sql.DataSource; import org.apache.seata.saga.engine.StateMachineConfig; import org.apache.seata.saga.engine.StateMachineEngine; diff --git a/spring/src/main/java/org/apache/seata/spring/annotation/GlobalTransactionScanner.java b/spring/src/main/java/org/apache/seata/spring/annotation/GlobalTransactionScanner.java index cc4c643a6cd..4ccee7dcd98 100644 --- a/spring/src/main/java/org/apache/seata/spring/annotation/GlobalTransactionScanner.java +++ b/spring/src/main/java/org/apache/seata/spring/annotation/GlobalTransactionScanner.java @@ -215,7 +215,7 @@ public void destroy() { ShutdownHook.getInstance().destroyAll(); } - private void initClient() { + protected void initClient() { if (LOGGER.isInfoEnabled()) { LOGGER.info("Initializing Global Transaction Clients ... "); } @@ -246,7 +246,7 @@ private void initClient() { } - private void registerSpringShutdownHook() { + protected void registerSpringShutdownHook() { if (applicationContext instanceof ConfigurableApplicationContext) { ((ConfigurableApplicationContext) applicationContext).registerShutdownHook(); ShutdownHook.removeRuntimeShutdownHook(); @@ -591,4 +591,20 @@ public static void addScannerExcludeBeanNames(String... beanNames) { EXCLUDE_BEAN_NAME_SET.addAll(Arrays.asList(beanNames)); } } + + public String getApplicationId() { + return applicationId; + } + + public String getTxServiceGroup() { + return txServiceGroup; + } + + public static String getAccessKey() { + return accessKey; + } + + public static String getSecretKey() { + return secretKey; + } } diff --git a/test/src/test/java/org/apache/seata/saga/engine/mock/DemoService.java b/test/src/test/java/org/apache/seata/saga/engine/mock/DemoService.java index 676106f510f..075990d0574 100644 --- a/test/src/test/java/org/apache/seata/saga/engine/mock/DemoService.java +++ b/test/src/test/java/org/apache/seata/saga/engine/mock/DemoService.java @@ -122,7 +122,7 @@ public Map randomExceptionMethod(Map input) { public static class People { private String name; - private int age; + private int age; private People[] childrenArray; private List childrenList;