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

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

64
saga/pom.xml Normal file
View File

@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 1999-2019 Seata.io Group.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>io.seata</groupId>
<artifactId>seata-parent</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>pom</packaging>
<artifactId>seata-saga</artifactId>
<name>seata-saga ${project.version}</name>
<modules>
<module>seata-saga-processctrl</module>
<module>seata-saga-statelang</module>
<module>seata-saga-engine</module>
<module>seata-saga-rm</module>
<module>seata-saga-tm</module>
<module>seata-saga-engine-store</module>
</modules>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>seata-common</artifactId>
<version>${project.parent.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 1999-2019 Seata.io Group.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>seata-saga</artifactId>
<groupId>io.seata</groupId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<name>seata-saga-engine-store ${project.version}</name>
<artifactId>seata-saga-engine-store</artifactId>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>seata-saga-engine</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>seata-saga-rm</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>seata-saga-tm</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>seata-serializer-all</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,200 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.config;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import io.seata.config.Configuration;
import io.seata.config.ConfigurationFactory;
import io.seata.core.constants.ConfigurationKeys;
import io.seata.saga.engine.impl.DefaultStateMachineConfig;
import io.seata.saga.engine.serializer.impl.ParamsSerializer;
import io.seata.saga.engine.store.db.DbAndReportTcStateLogStore;
import io.seata.saga.engine.store.db.DbStateLangStore;
import io.seata.saga.tm.DefaultSagaTransactionalTemplate;
import io.seata.saga.tm.SagaTransactionalTemplate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.util.StringUtils;
import static io.seata.common.DefaultValues.DEFAULT_CLIENT_REPORT_SUCCESS_ENABLE;
import static io.seata.common.DefaultValues.DEFAULT_CLIENT_SAGA_BRANCH_REGISTER_ENABLE;
import static io.seata.common.DefaultValues.DEFAULT_CLIENT_SAGA_COMPENSATE_PERSIST_MODE_UPDATE;
import static io.seata.common.DefaultValues.DEFAULT_CLIENT_SAGA_RETRY_PERSIST_MODE_UPDATE;
import static io.seata.common.DefaultValues.DEFAULT_SAGA_JSON_PARSER;
/**
* DbStateMachineConfig
*
* @author lorne.cl
*/
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 tablePrefix = "seata_";
private String dbType;
private SagaTransactionalTemplate sagaTransactionalTemplate;
private boolean rmReportSuccessEnable = DEFAULT_CLIENT_REPORT_SUCCESS_ENABLE;
private boolean sagaBranchRegisterEnable = DEFAULT_CLIENT_SAGA_BRANCH_REGISTER_ENABLE;
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);
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 {
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.afterPropertiesSet();
sagaTransactionalTemplate = defaultSagaTransactionalTemplate;
}
dbStateLogStore.setSagaTransactionalTemplate(sagaTransactionalTemplate);
setStateLogStore(dbStateLogStore);
}
if (getStateLangStore() == null) {
DbStateLangStore dbStateLangStore = new DbStateLangStore();
dbStateLangStore.setDataSource(dataSource);
dbStateLangStore.setTablePrefix(tablePrefix);
dbStateLangStore.setDbType(dbType);
setStateLangStore(dbStateLangStore);
}
super.afterPropertiesSet();//must execute after StateLangStore initialized
}
@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 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 setSagaBranchRegisterEnable(boolean sagaBranchRegisterEnable) {
this.sagaBranchRegisterEnable = sagaBranchRegisterEnable;
}
public void setRmReportSuccessEnable(boolean rmReportSuccessEnable) {
this.rmReportSuccessEnable = rmReportSuccessEnable;
}
}

View File

@@ -0,0 +1,107 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.pcext.interceptors;
import io.seata.common.loader.LoadLevel;
import io.seata.common.util.StringUtils;
import io.seata.core.context.RootContext;
import io.seata.core.model.BranchType;
import io.seata.saga.engine.exception.EngineExecutionException;
import io.seata.saga.engine.pcext.InterceptableStateHandler;
import io.seata.saga.engine.pcext.StateHandlerInterceptor;
import io.seata.saga.proctrl.ProcessContext;
import io.seata.saga.statelang.domain.DomainConstants;
import io.seata.saga.statelang.domain.StateMachineInstance;
import io.seata.tm.api.GlobalTransaction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Map;
/**
* InSagaBranchHandler Interceptor
*
* @author wang.liang
*/
@LoadLevel(name = "InSagaBranch", order = 50)
public class InSagaBranchHandlerInterceptor implements StateHandlerInterceptor {
private static final Logger LOGGER = LoggerFactory.getLogger(InSagaBranchHandlerInterceptor.class);
@Override
public boolean match(Class<? extends InterceptableStateHandler> clazz) {
// This interceptor will intercept all types of InterceptableStateHandler
return clazz != null;
}
@Override
public void preProcess(ProcessContext context) throws EngineExecutionException {
// get xid
String xid = this.getXidFromProcessContext(context);
if (StringUtils.isBlank(xid)) {
LOGGER.warn("There is no xid in the process context.");
return;
}
// logger.warn if previousXid is not equals to xid
if (LOGGER.isWarnEnabled()) {
String previousXid = RootContext.getXID();
if (previousXid != null) {
if (!StringUtils.equalsIgnoreCase(previousXid, xid)) {
LOGGER.warn("xid in change from {} to {}, Please don't use state machine engine in other global transaction.",
previousXid, xid);
}
}
}
// bind xid and branchType
RootContext.bind(xid);
RootContext.bindBranchType(BranchType.SAGA);
if (LOGGER.isInfoEnabled()) {
LOGGER.info("[{}] Begin process the state instance in the saga branch.", xid);
}
}
@Override
public void postProcess(ProcessContext context, Exception exp) throws EngineExecutionException {
// unbind xid and branchType
String xid = RootContext.unbind();
RootContext.unbindBranchType();
if (LOGGER.isInfoEnabled()) {
LOGGER.info("[{}] Finish process the state instance in the saga branch.", xid);
}
}
/**
* Gets xid from saga process context.
*
* @return the xid
*/
protected String getXidFromProcessContext(ProcessContext context) {
String xid = null;
Map<String, Object> contextVariable = (Map<String, Object>) context.getVariable(DomainConstants.VAR_NAME_STATEMACHINE_CONTEXT);
if (contextVariable != null && contextVariable.containsKey(DomainConstants.VAR_NAME_GLOBAL_TX)) {
GlobalTransaction globalTransaction = (GlobalTransaction) contextVariable.get(DomainConstants.VAR_NAME_GLOBAL_TX);
xid = globalTransaction.getXid();
} else {
StateMachineInstance stateMachineInstance = (StateMachineInstance) context.getVariable(DomainConstants.VAR_NAME_STATEMACHINE_INST);
if (stateMachineInstance != null) {
xid = stateMachineInstance.getId();
}
}
return xid;
}
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.serializer;
/**
* Object serializer
*
* @author lorne.cl
*/
public interface Serializer<S, T> {
/**
* serialize
*
* @param object
* @return
*/
T serialize(S object);
/**
* deserialize
*
* @param obj
* @return
*/
S deserialize(T obj);
}

View File

@@ -0,0 +1,93 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.serializer.impl;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import io.seata.saga.engine.serializer.Serializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Exception serializer
*
* @author lorne.cl
*/
public class ExceptionSerializer implements Serializer<Exception, byte[]> {
private static final Logger LOGGER = LoggerFactory.getLogger(ExceptionSerializer.class);
public static byte[] serializeByObjectOutput(Object o) {
byte[] result = null;
if (o != null) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (ObjectOutputStream oos = new ObjectOutputStream(baos)) {
oos.writeObject(o);
oos.flush();
result = baos.toByteArray();
} catch (IOException e) {
LOGGER.error("serializer failed: {}", o.getClass(), e);
throw new RuntimeException("IO Create Error", e);
}
}
return result;
}
public static <T> T deserializeByObjectInputStream(byte[] bytes, Class<T> valueType) {
if (bytes == null) {
return null;
}
Object result = deserializeByObjectInputStream(bytes);
return valueType.cast(result);
}
public static Object deserializeByObjectInputStream(byte[] bytes) {
Object result = null;
if (bytes != null) {
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
try (ObjectInputStream ois = new ObjectInputStream(bais)) {
result = ois.readObject();
} catch (IOException e) {
LOGGER.error("deserialize failed:", e);
throw new RuntimeException("IO Create Error", e);
} catch (ClassNotFoundException e) {
LOGGER.error("deserialize failed:", e);
throw new RuntimeException("Cannot find specified class", e);
}
}
return result;
}
@Override
public byte[] serialize(Exception object) {
return serializeByObjectOutput(object);
}
@Override
public Exception deserialize(byte[] bytes) {
return deserializeByObjectInputStream(bytes, Exception.class);
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.serializer.impl;
import io.seata.saga.engine.serializer.Serializer;
import io.seata.saga.statelang.domain.DomainConstants;
import io.seata.saga.statelang.parser.JsonParser;
import io.seata.saga.statelang.parser.JsonParserFactory;
/**
* Parameter serializer based on Fastjson
*
* @author lorne.cl
*/
public class ParamsSerializer implements Serializer<Object, String> {
private String jsonParserName = DomainConstants.DEFAULT_JSON_PARSER;
@Override
public String serialize(Object params) {
if (params != null) {
JsonParser jsonParser = JsonParserFactory.getJsonParser(jsonParserName);
if (jsonParser == null) {
throw new RuntimeException("Cannot find JsonParer by name: " + jsonParserName);
}
return jsonParser.toJsonString(params, false);
}
return null;
}
@Override
public Object deserialize(String json) {
if (json != null) {
JsonParser jsonParser = JsonParserFactory.getJsonParser(jsonParserName);
if (jsonParser == null) {
throw new RuntimeException("Cannot find JsonParer by name: " + jsonParserName);
}
return jsonParser.parse(json, Object.class, false);
}
return null;
}
public String getJsonParserName() {
return jsonParserName;
}
public void setJsonParserName(String jsonParserName) {
this.jsonParserName = jsonParserName;
}
}

View File

@@ -0,0 +1,210 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.store.db;
import io.seata.common.util.BeanUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.sql.DataSource;
import io.seata.common.exception.StoreException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Abstract store
*
* @author lorne.cl
*/
public class AbstractStore {
protected static final Logger LOGGER = LoggerFactory.getLogger(AbstractStore.class);
protected DataSource dataSource;
protected String dbType;
protected String tablePrefix;
public static void closeSilent(AutoCloseable closeable) {
if (closeable != null) {
try {
closeable.close();
} catch (Exception e) {
LOGGER.info(e.getMessage(), e);
}
}
}
protected <T> T selectOne(String sql, ResultSetToObject<T> resultSetToObject, Object... args) {
Connection connection = null;
PreparedStatement stmt = null;
ResultSet resultSet = null;
try {
connection = dataSource.getConnection();
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Preparing SQL statement: {}", sql);
}
stmt = connection.prepareStatement(sql);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("setting params to PreparedStatement: {}", Arrays.toString(args));
}
for (int i = 0; i < args.length; i++) {
stmt.setObject(i + 1, args[i]);
}
resultSet = stmt.executeQuery();
if (resultSet.next()) {
return resultSetToObject.toObject(resultSet);
}
} catch (SQLException e) {
throw new StoreException(e);
} finally {
closeSilent(resultSet);
closeSilent(stmt);
closeSilent(connection);
}
return null;
}
protected <T> List<T> selectList(String sql, ResultSetToObject<T> resultSetToObject, Object... args) {
Connection connection = null;
PreparedStatement stmt = null;
ResultSet resultSet = null;
try {
connection = dataSource.getConnection();
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Preparing SQL: {}", sql);
}
stmt = connection.prepareStatement(sql);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("setting params to PreparedStatement: {}", Arrays.toString(args));
}
for (int i = 0; i < args.length; i++) {
stmt.setObject(i + 1, args[i]);
}
resultSet = stmt.executeQuery();
List<T> list = new ArrayList<>();
while (resultSet.next()) {
list.add(resultSetToObject.toObject(resultSet));
}
return list;
} catch (SQLException e) {
throw new StoreException(e);
} finally {
closeSilent(resultSet);
closeSilent(stmt);
closeSilent(connection);
}
}
protected <T> int executeUpdate(String sql, ObjectToStatement<T> objectToStatement, T o) {
Connection connection = null;
PreparedStatement stmt = null;
try {
connection = dataSource.getConnection();
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Preparing SQL: {}", sql);
}
stmt = connection.prepareStatement(sql);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("setting params to PreparedStatement: {}", BeanUtils.beanToString(o));
}
objectToStatement.toStatement(o, stmt);
int count = stmt.executeUpdate();
if (!connection.getAutoCommit()) {
connection.commit();
}
return count;
} catch (SQLException e) {
throw new StoreException(e);
} finally {
closeSilent(stmt);
closeSilent(connection);
}
}
protected int executeUpdate(String sql, Object... args) {
Connection connection = null;
PreparedStatement stmt = null;
try {
connection = dataSource.getConnection();
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Preparing SQL: {}", sql);
}
stmt = connection.prepareStatement(sql);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("setting params to PreparedStatement: {}", Arrays.toString(args));
}
for (int i = 0; i < args.length; i++) {
stmt.setObject(i + 1, args[i]);
}
int count = stmt.executeUpdate();
if (!connection.getAutoCommit()) {
connection.commit();
}
return count;
} catch (SQLException e) {
throw new StoreException(e);
} finally {
closeSilent(stmt);
closeSilent(connection);
}
}
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
public void setDbType(String dbType) {
this.dbType = dbType;
}
public void setTablePrefix(String tablePrefix) {
this.tablePrefix = tablePrefix;
}
protected interface ResultSetToObject<T> {
T toObject(ResultSet resultSet) throws SQLException;
}
protected interface ObjectToStatement<T> {
void toStatement(T o, PreparedStatement statement) throws SQLException;
}
}

View File

@@ -0,0 +1,912 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.store.db;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import io.seata.common.Constants;
import io.seata.common.exception.FrameworkErrorCode;
import io.seata.common.exception.StoreException;
import io.seata.common.util.CollectionUtils;
import io.seata.core.context.RootContext;
import io.seata.core.exception.TransactionException;
import io.seata.core.model.BranchStatus;
import io.seata.core.model.BranchType;
import io.seata.core.model.GlobalStatus;
import io.seata.saga.engine.StateMachineConfig;
import io.seata.saga.engine.config.DbStateMachineConfig;
import io.seata.saga.engine.exception.EngineExecutionException;
import io.seata.saga.engine.impl.DefaultStateMachineConfig;
import io.seata.saga.engine.pcext.StateInstruction;
import io.seata.saga.engine.pcext.utils.EngineUtils;
import io.seata.saga.engine.sequence.SeqGenerator;
import io.seata.saga.engine.serializer.Serializer;
import io.seata.saga.engine.serializer.impl.ExceptionSerializer;
import io.seata.saga.engine.serializer.impl.ParamsSerializer;
import io.seata.saga.engine.store.StateLogStore;
import io.seata.saga.proctrl.ProcessContext;
import io.seata.saga.statelang.domain.DomainConstants;
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 io.seata.saga.statelang.domain.impl.ServiceTaskStateImpl;
import io.seata.saga.statelang.domain.impl.StateInstanceImpl;
import io.seata.saga.statelang.domain.impl.StateMachineInstanceImpl;
import io.seata.saga.tm.SagaTransactionalTemplate;
import io.seata.tm.api.GlobalTransaction;
import io.seata.tm.api.TransactionalExecutor.ExecutionException;
import io.seata.tm.api.transaction.TransactionInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
/**
* State machine logs and definitions persist to database and report status to TC (Transaction Coordinator)
*
* @author lorne.cl
*/
public class DbAndReportTcStateLogStore extends AbstractStore implements StateLogStore {
private static final Logger LOGGER = LoggerFactory.getLogger(
DbAndReportTcStateLogStore.class);
private static final StateMachineInstanceToStatementForInsert STATE_MACHINE_INSTANCE_TO_STATEMENT_FOR_INSERT
= new StateMachineInstanceToStatementForInsert();
private static final StateMachineInstanceToStatementForUpdate STATE_MACHINE_INSTANCE_TO_STATEMENT_FOR_UPDATE
= new StateMachineInstanceToStatementForUpdate();
private static final ResultSetToStateMachineInstance RESULT_SET_TO_STATE_MACHINE_INSTANCE
= new ResultSetToStateMachineInstance();
private static final StateInstanceToStatementForInsert STATE_INSTANCE_TO_STATEMENT_FOR_INSERT
= new StateInstanceToStatementForInsert();
private static final StateInstanceToStatementForUpdate STATE_INSTANCE_TO_STATEMENT_FOR_UPDATE
= new StateInstanceToStatementForUpdate();
private static final ResultSetToStateInstance RESULT_SET_TO_STATE_INSTANCE = new ResultSetToStateInstance();
private SagaTransactionalTemplate sagaTransactionalTemplate;
private Serializer<Object, String> paramsSerializer = new ParamsSerializer();
private Serializer<Exception, byte[]> exceptionSerializer = new ExceptionSerializer();
private StateLogStoreSqls stateLogStoreSqls;
private String defaultTenantId;
private SeqGenerator seqGenerator;
@Override
public void recordStateMachineStarted(StateMachineInstance machineInstance, ProcessContext context) {
if (machineInstance != null) {
//if parentId is not null, machineInstance is a SubStateMachine, do not start a new global transaction,
//use parent transaction instead.
String parentId = machineInstance.getParentId();
if (StringUtils.isEmpty(parentId)) {
beginTransaction(machineInstance, context);
}
try {
if (StringUtils.isEmpty(machineInstance.getId()) && seqGenerator != null) {
machineInstance.setId(seqGenerator.generate(DomainConstants.SEQ_ENTITY_STATE_MACHINE_INST));
}
// bind SAGA branch type
RootContext.bindBranchType(BranchType.SAGA);
// save to db
machineInstance.setSerializedStartParams(paramsSerializer.serialize(machineInstance.getStartParams()));
int effect = executeUpdate(stateLogStoreSqls.getRecordStateMachineStartedSql(dbType),
STATE_MACHINE_INSTANCE_TO_STATEMENT_FOR_INSERT, machineInstance);
if (effect < 1) {
throw new StoreException("StateMachineInstance record start error, Xid: " + machineInstance.getId(),
FrameworkErrorCode.OperationDenied);
}
} catch (StoreException e) {
LOGGER.error("Record statemachine start error: {}, StateMachine: {}, XID: {}, Reason: {}",
e.getErrcode(), machineInstance.getStateMachine().getName(), machineInstance.getId(), e.getMessage(), e);
// clear
RootContext.unbind();
RootContext.unbindBranchType();
if (sagaTransactionalTemplate != null) {
sagaTransactionalTemplate.cleanUp();
}
throw e;
}
}
}
protected void beginTransaction(StateMachineInstance machineInstance, ProcessContext context) {
if (sagaTransactionalTemplate != null) {
StateMachineConfig stateMachineConfig = (StateMachineConfig) context.getVariable(
DomainConstants.VAR_NAME_STATEMACHINE_CONFIG);
TransactionInfo transactionInfo = new TransactionInfo();
transactionInfo.setTimeOut(stateMachineConfig.getTransOperationTimeout());
transactionInfo.setName(Constants.SAGA_TRANS_NAME_PREFIX + machineInstance.getStateMachine().getName());
try {
GlobalTransaction globalTransaction = sagaTransactionalTemplate.beginTransaction(transactionInfo);
machineInstance.setId(globalTransaction.getXid());
context.setVariable(DomainConstants.VAR_NAME_GLOBAL_TX, globalTransaction);
Map<String, Object> machineContext = machineInstance.getContext();
if (machineContext != null) {
machineContext.put(DomainConstants.VAR_NAME_GLOBAL_TX, globalTransaction);
}
} catch (ExecutionException e) {
String xid = null;
if (e.getTransaction() != null) {
xid = e.getTransaction().getXid();
}
throw new EngineExecutionException(e,
e.getCode() + ", TransName:" + transactionInfo.getName() + ", XID: " + xid + ", Reason: " + e
.getMessage(), FrameworkErrorCode.TransactionManagerError);
}
finally {
if (Boolean.TRUE.equals(context.getVariable(DomainConstants.VAR_NAME_IS_ASYNC_EXECUTION))) {
RootContext.unbind();
RootContext.unbindBranchType();
}
}
}
}
@Override
public void recordStateMachineFinished(StateMachineInstance machineInstance, ProcessContext context) {
if (machineInstance != null) {
try {
// save to db
Map<String, Object> endParams = machineInstance.getEndParams();
if (endParams != null) {
endParams.remove(DomainConstants.VAR_NAME_GLOBAL_TX);
}
// if success, clear exception
if (ExecutionStatus.SU.equals(machineInstance.getStatus()) && machineInstance.getException() != null) {
machineInstance.setException(null);
}
machineInstance.setSerializedEndParams(paramsSerializer.serialize(machineInstance.getEndParams()));
machineInstance.setSerializedException(exceptionSerializer.serialize(machineInstance.getException()));
int effect = executeUpdate(stateLogStoreSqls.getRecordStateMachineFinishedSql(dbType),
STATE_MACHINE_INSTANCE_TO_STATEMENT_FOR_UPDATE, machineInstance);
if (effect < 1) {
LOGGER.warn("StateMachineInstance[{}] is recovery by server, skip recordStateMachineFinished.", machineInstance.getId());
} else {
StateMachineConfig stateMachineConfig = (StateMachineConfig) context.getVariable(
DomainConstants.VAR_NAME_STATEMACHINE_CONFIG);
if (EngineUtils.isTimeout(machineInstance.getGmtUpdated(), stateMachineConfig.getTransOperationTimeout())) {
LOGGER.warn("StateMachineInstance[{}] is execution timeout, skip report transaction finished to server.", machineInstance.getId());
} else if (StringUtils.isEmpty(machineInstance.getParentId())) {
//if parentId is not null, machineInstance is a SubStateMachine, do not report global transaction.
reportTransactionFinished(machineInstance, context);
}
}
} finally {
RootContext.unbind();
RootContext.unbindBranchType();
}
}
}
protected void reportTransactionFinished(StateMachineInstance machineInstance, ProcessContext context) {
if (sagaTransactionalTemplate != null) {
try {
GlobalTransaction globalTransaction = getGlobalTransaction(machineInstance, context);
if (globalTransaction == null) {
throw new EngineExecutionException("Global transaction is not exists",
FrameworkErrorCode.ObjectNotExists);
}
GlobalStatus globalStatus;
if (ExecutionStatus.SU.equals(machineInstance.getStatus())
&& machineInstance.getCompensationStatus() == null) {
globalStatus = GlobalStatus.Committed;
} else if (ExecutionStatus.SU.equals(machineInstance.getCompensationStatus())) {
globalStatus = GlobalStatus.Rollbacked;
} else if (ExecutionStatus.FA.equals(machineInstance.getCompensationStatus()) || ExecutionStatus.UN
.equals(machineInstance.getCompensationStatus())) {
globalStatus = GlobalStatus.RollbackRetrying;
} else if (ExecutionStatus.FA.equals(machineInstance.getStatus())
&& machineInstance.getCompensationStatus() == null) {
globalStatus = GlobalStatus.Finished;
} else if (ExecutionStatus.UN.equals(machineInstance.getStatus())
&& machineInstance.getCompensationStatus() == null) {
globalStatus = GlobalStatus.CommitRetrying;
} else {
globalStatus = GlobalStatus.UnKnown;
}
sagaTransactionalTemplate.reportTransaction(globalTransaction, globalStatus);
} catch (ExecutionException e) {
LOGGER.error("Report transaction finish to server error: {}, StateMachine: {}, XID: {}, Reason: {}",
e.getCode(), machineInstance.getStateMachine().getName(), machineInstance.getId(), e.getMessage(), e);
} catch (TransactionException e) {
LOGGER.error("Report transaction finish to server error: {}, StateMachine: {}, XID: {}, Reason: {}",
e.getCode(), machineInstance.getStateMachine().getName(), machineInstance.getId(), e.getMessage(), e);
} finally {
// clear
RootContext.unbind();
RootContext.unbindBranchType();
sagaTransactionalTemplate.triggerAfterCompletion();
sagaTransactionalTemplate.cleanUp();
}
}
}
@Override
public void recordStateMachineRestarted(StateMachineInstance machineInstance, ProcessContext context) {
if (machineInstance != null) {
//save to db
Date gmtUpdated = new Date();
int effect = executeUpdate(stateLogStoreSqls.getUpdateStateMachineRunningStatusSql(dbType), machineInstance.isRunning(), new Timestamp(gmtUpdated.getTime()),
machineInstance.getId(), new Timestamp(machineInstance.getGmtUpdated().getTime()));
if (effect < 1) {
throw new EngineExecutionException(
"StateMachineInstance [id:" + machineInstance.getId() + "] is recovered by an other execution, restart denied", FrameworkErrorCode.OperationDenied);
}
machineInstance.setGmtUpdated(gmtUpdated);
}
}
@Override
public void recordStateStarted(StateInstance stateInstance, ProcessContext context) {
if (stateInstance != null) {
boolean isUpdateMode = isUpdateMode(stateInstance, context);
// if this state is for retry, do not register branch
if (StringUtils.hasLength(stateInstance.getStateIdRetriedFor())) {
if (isUpdateMode) {
stateInstance.setId(stateInstance.getStateIdRetriedFor());
} else {
// generate id by default
stateInstance.setId(generateRetryStateInstanceId(stateInstance));
}
}
// if this state is for compensation, do not register branch
else if (StringUtils.hasLength(stateInstance.getStateIdCompensatedFor())) {
stateInstance.setId(generateCompensateStateInstanceId(stateInstance, isUpdateMode));
} else {
branchRegister(stateInstance, context);
}
if (StringUtils.isEmpty(stateInstance.getId()) && seqGenerator != null) {
stateInstance.setId(seqGenerator.generate(DomainConstants.SEQ_ENTITY_STATE_INST));
}
stateInstance.setSerializedInputParams(paramsSerializer.serialize(stateInstance.getInputParams()));
if (!isUpdateMode) {
executeUpdate(stateLogStoreSqls.getRecordStateStartedSql(dbType),
STATE_INSTANCE_TO_STATEMENT_FOR_INSERT, stateInstance);
} else {
// if this retry/compensate state do not need persist, just update last inst
executeUpdate(stateLogStoreSqls.getUpdateStateExecutionStatusSql(dbType),
stateInstance.getStatus().name(), new Timestamp(System.currentTimeMillis()),
stateInstance.getMachineInstanceId(), stateInstance.getId());
}
}
}
protected void branchRegister(StateInstance stateInstance, ProcessContext context) {
if (sagaTransactionalTemplate != null) {
StateMachineConfig stateMachineConfig = (StateMachineConfig) context.getVariable(
DomainConstants.VAR_NAME_STATEMACHINE_CONFIG);
if (stateMachineConfig instanceof DbStateMachineConfig
&& !((DbStateMachineConfig)stateMachineConfig).isSagaBranchRegisterEnable()) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("sagaBranchRegisterEnable = false, skip register branch. state[" + stateInstance.getName() + "]");
}
return;
}
//Register branch
try {
StateMachineInstance machineInstance = stateInstance.getStateMachineInstance();
GlobalTransaction globalTransaction = getGlobalTransaction(machineInstance, context);
if (globalTransaction == null) {
throw new EngineExecutionException("Global transaction is not exists", FrameworkErrorCode.ObjectNotExists);
}
String resourceId = stateInstance.getStateMachineInstance().getStateMachine().getName() + "#" + stateInstance.getName();
long branchId = sagaTransactionalTemplate.branchRegister(resourceId, null, globalTransaction.getXid(), null, null);
stateInstance.setId(String.valueOf(branchId));
} catch (TransactionException e) {
throw new EngineExecutionException(e,
"Branch transaction error: " + e.getCode() + ", StateMachine:" + stateInstance.getStateMachineInstance()
.getStateMachine().getName() + ", XID: " + stateInstance.getStateMachineInstance().getId() + ", State:"
+ stateInstance.getName() + ", stateId: " + stateInstance.getId() + ", Reason: " + e.getMessage(),
FrameworkErrorCode.TransactionManagerError);
} catch (ExecutionException e) {
throw new EngineExecutionException(e,
"Branch transaction error: " + e.getCode() + ", StateMachine:" + stateInstance.getStateMachineInstance()
.getStateMachine().getName() + ", XID: " + stateInstance.getStateMachineInstance().getId() + ", State:"
+ stateInstance.getName() + ", stateId: " + stateInstance.getId() + ", Reason: " + e.getMessage(),
FrameworkErrorCode.TransactionManagerError);
}
}
}
protected GlobalTransaction getGlobalTransaction(StateMachineInstance machineInstance, ProcessContext context)
throws ExecutionException, TransactionException {
GlobalTransaction globalTransaction = (GlobalTransaction) context.getVariable(DomainConstants.VAR_NAME_GLOBAL_TX);
if (globalTransaction == null) {
String xid;
String parentId = machineInstance.getParentId();
if (StringUtils.isEmpty(parentId)) {
xid = machineInstance.getId();
} else {
xid = parentId.substring(0, parentId.lastIndexOf(DomainConstants.SEPERATOR_PARENT_ID));
}
globalTransaction = sagaTransactionalTemplate.reloadTransaction(xid);
if (globalTransaction != null) {
context.setVariable(DomainConstants.VAR_NAME_GLOBAL_TX, globalTransaction);
}
}
return globalTransaction;
}
/**
* generate retry state instance id based on original state instance id
* ${originalStateInstanceId}.${retryCount}
* @param stateInstance
* @return
*/
private String generateRetryStateInstanceId(StateInstance stateInstance) {
String originalStateInstId = stateInstance.getStateIdRetriedFor();
int maxIndex = 1;
Map<String, StateInstance> stateInstanceMap = stateInstance.getStateMachineInstance().getStateMap();
StateInstance originalStateInst = stateInstanceMap.get(stateInstance.getStateIdRetriedFor());
while (StringUtils.hasLength(originalStateInst.getStateIdRetriedFor())) {
originalStateInst = stateInstanceMap.get(originalStateInst.getStateIdRetriedFor());
int idIndex = getIdIndex(originalStateInst.getId(), ".");
maxIndex = idIndex > maxIndex ? idIndex : maxIndex;
maxIndex++;
}
if (originalStateInst != null) {
originalStateInstId = originalStateInst.getId();
}
return originalStateInstId + "." + maxIndex;
}
/**
* generate compensate state instance id based on original state instance id
* ${originalStateInstanceId}-${retryCount}
* @param stateInstance
* @return
*/
private String generateCompensateStateInstanceId(StateInstance stateInstance, boolean isUpdateMode) {
String originalCompensateStateInstId = stateInstance.getStateIdCompensatedFor();
int maxIndex = 1;
// if update mode, means update last compensate inst
if (isUpdateMode) {
return originalCompensateStateInstId + "-" + maxIndex;
}
for (int i = 0; i < stateInstance.getStateMachineInstance().getStateList().size(); i++) {
StateInstance aStateInstance = stateInstance.getStateMachineInstance().getStateList().get(i);
if (aStateInstance != stateInstance
&& originalCompensateStateInstId.equals(aStateInstance.getStateIdCompensatedFor())) {
int idIndex = getIdIndex(aStateInstance.getId(), "-");
maxIndex = idIndex > maxIndex ? idIndex : maxIndex;
maxIndex++;
}
}
return originalCompensateStateInstId + "-" + maxIndex;
}
private int getIdIndex(String stateInstanceId, String separator) {
if (StringUtils.hasLength(stateInstanceId)) {
int start = stateInstanceId.lastIndexOf(separator);
if (start > 0) {
String indexStr = stateInstanceId.substring(start + 1, stateInstanceId.length());
try {
return Integer.parseInt(indexStr);
} catch (NumberFormatException e) {
LOGGER.warn("get stateInstance id index failed", e);
}
}
}
return -1;
}
private boolean isUpdateMode(StateInstance stateInstance, ProcessContext context) {
DefaultStateMachineConfig stateMachineConfig = (DefaultStateMachineConfig)context.getVariable(
DomainConstants.VAR_NAME_STATEMACHINE_CONFIG);
StateInstruction instruction = context.getInstruction(StateInstruction.class);
ServiceTaskStateImpl state = (ServiceTaskStateImpl)instruction.getState(context);
StateMachine stateMachine = stateInstance.getStateMachineInstance().getStateMachine();
if (StringUtils.hasLength(stateInstance.getStateIdRetriedFor())) {
if (null != state.isRetryPersistModeUpdate()) {
return state.isRetryPersistModeUpdate();
} else if (null != stateMachine.isRetryPersistModeUpdate()) {
return stateMachine.isRetryPersistModeUpdate();
}
return stateMachineConfig.isSagaRetryPersistModeUpdate();
} else if (StringUtils.hasLength(stateInstance.getStateIdCompensatedFor())) {
// find if this compensate has been executed
for (int i = 0; i < stateInstance.getStateMachineInstance().getStateList().size(); i++) {
StateInstance aStateInstance = stateInstance.getStateMachineInstance().getStateList().get(i);
if (aStateInstance.isForCompensation() && aStateInstance.getName().equals(stateInstance.getName())) {
if (null != state.isCompensatePersistModeUpdate()) {
return state.isCompensatePersistModeUpdate();
} else if (null != stateMachine.isCompensatePersistModeUpdate()) {
return stateMachine.isCompensatePersistModeUpdate();
}
return stateMachineConfig.isSagaCompensatePersistModeUpdate();
}
}
return false;
}
return false;
}
@Override
public void recordStateFinished(StateInstance stateInstance, ProcessContext context) {
if (stateInstance != null) {
stateInstance.setSerializedOutputParams(paramsSerializer.serialize(stateInstance.getOutputParams()));
stateInstance.setSerializedException(exceptionSerializer.serialize(stateInstance.getException()));
executeUpdate(stateLogStoreSqls.getRecordStateFinishedSql(dbType), STATE_INSTANCE_TO_STATEMENT_FOR_UPDATE,
stateInstance);
//A switch to skip branch report on branch success, in order to optimize performance
StateMachineConfig stateMachineConfig = (StateMachineConfig) context.getVariable(
DomainConstants.VAR_NAME_STATEMACHINE_CONFIG);
if (!(stateMachineConfig instanceof DbStateMachineConfig
&& !((DbStateMachineConfig)stateMachineConfig).isRmReportSuccessEnable()
&& ExecutionStatus.SU.equals(stateInstance.getStatus()))) {
branchReport(stateInstance, context);
}
}
}
protected void branchReport(StateInstance stateInstance, ProcessContext context) {
if (sagaTransactionalTemplate != null) {
StateMachineConfig stateMachineConfig = (StateMachineConfig) context.getVariable(
DomainConstants.VAR_NAME_STATEMACHINE_CONFIG);
if (stateMachineConfig instanceof DbStateMachineConfig
&& !((DbStateMachineConfig)stateMachineConfig).isSagaBranchRegisterEnable()) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("sagaBranchRegisterEnable = false, skip branch report. state[" + stateInstance.getName() + "]");
}
return;
}
BranchStatus branchStatus = null;
//find out the original state instance, only the original state instance is registered on the server, and its status should
// be reported.
StateInstance originalStateInst = null;
if (StringUtils.hasLength(stateInstance.getStateIdRetriedFor())) {
if (isUpdateMode(stateInstance, context)) {
originalStateInst = stateInstance;
} else {
originalStateInst = findOutOriginalStateInstanceOfRetryState(stateInstance);
}
if (ExecutionStatus.SU.equals(stateInstance.getStatus())) {
branchStatus = BranchStatus.PhaseTwo_Committed;
} else if (ExecutionStatus.FA.equals(stateInstance.getStatus()) || ExecutionStatus.UN.equals(
stateInstance.getStatus())) {
branchStatus = BranchStatus.PhaseOne_Failed;
} else {
branchStatus = BranchStatus.Unknown;
}
} else if (StringUtils.hasLength(stateInstance.getStateIdCompensatedFor())) {
if (isUpdateMode(stateInstance, context)) {
originalStateInst = stateInstance.getStateMachineInstance().getStateMap().get(
stateInstance.getStateIdCompensatedFor());
} else {
originalStateInst = findOutOriginalStateInstanceOfCompensateState(stateInstance);
}
}
if (originalStateInst == null) {
originalStateInst = stateInstance;
}
if (branchStatus == null) {
if (ExecutionStatus.SU.equals(originalStateInst.getStatus()) && originalStateInst.getCompensationStatus() == null) {
branchStatus = BranchStatus.PhaseTwo_Committed;
} else if (ExecutionStatus.SU.equals(originalStateInst.getCompensationStatus())) {
branchStatus = BranchStatus.PhaseTwo_Rollbacked;
} else if (ExecutionStatus.FA.equals(originalStateInst.getCompensationStatus())
|| ExecutionStatus.UN.equals(originalStateInst.getCompensationStatus())) {
branchStatus = BranchStatus.PhaseTwo_RollbackFailed_Retryable;
} else if ((ExecutionStatus.FA.equals(originalStateInst.getStatus()) || ExecutionStatus.UN.equals(
originalStateInst.getStatus()))
&& originalStateInst.getCompensationStatus() == null) {
branchStatus = BranchStatus.PhaseOne_Failed;
} else {
branchStatus = BranchStatus.Unknown;
}
}
try {
StateMachineInstance machineInstance = stateInstance.getStateMachineInstance();
GlobalTransaction globalTransaction = getGlobalTransaction(machineInstance, context);
if (globalTransaction == null) {
throw new EngineExecutionException("Global transaction is not exists", FrameworkErrorCode.ObjectNotExists);
}
sagaTransactionalTemplate.branchReport(globalTransaction.getXid(), Long.parseLong(originalStateInst.getId()), branchStatus,
null);
} catch (TransactionException e) {
LOGGER.error(
"Report branch status to server error: {}, StateMachine:{}, StateName:{}, XID: {}, branchId: {}, branchStatus:{},"
+ " Reason:{} "
, e.getCode()
, originalStateInst.getStateMachineInstance().getStateMachine().getName()
, originalStateInst.getName()
, originalStateInst.getStateMachineInstance().getId()
, originalStateInst.getId()
, branchStatus
, e.getMessage()
, e);
} catch (ExecutionException e) {
LOGGER.error(
"Report branch status to server error: {}, StateMachine:{}, StateName:{}, XID: {}, branchId: {}, branchStatus:{},"
+ " Reason:{} "
, e.getCode()
, originalStateInst.getStateMachineInstance().getStateMachine().getName()
, originalStateInst.getName()
, originalStateInst.getStateMachineInstance().getId()
, originalStateInst.getId()
, branchStatus
, e.getMessage()
, e);
}
}
}
private StateInstance findOutOriginalStateInstanceOfRetryState(StateInstance stateInstance) {
StateInstance originalStateInst;
Map<String, StateInstance> stateInstanceMap = stateInstance.getStateMachineInstance().getStateMap();
originalStateInst = stateInstanceMap.get(stateInstance.getStateIdRetriedFor());
while (StringUtils.hasLength(originalStateInst.getStateIdRetriedFor())) {
originalStateInst = stateInstanceMap.get(originalStateInst.getStateIdRetriedFor());
}
return originalStateInst;
}
private StateInstance findOutOriginalStateInstanceOfCompensateState(StateInstance stateInstance) {
StateInstance originalStateInst;
Map<String, StateInstance> stateInstanceMap = stateInstance.getStateMachineInstance().getStateMap();
originalStateInst = stateInstance.getStateMachineInstance().getStateMap().get(stateInstance.getStateIdCompensatedFor());
while (StringUtils.hasLength(originalStateInst.getStateIdRetriedFor())) {
originalStateInst = stateInstanceMap.get(originalStateInst.getStateIdRetriedFor());
}
return originalStateInst;
}
@Override
public StateMachineInstance getStateMachineInstance(String stateMachineInstanceId) {
StateMachineInstance stateMachineInstance = selectOne(stateLogStoreSqls.getGetStateMachineInstanceByIdSql(dbType),
RESULT_SET_TO_STATE_MACHINE_INSTANCE, stateMachineInstanceId);
if (stateMachineInstance == null) {
return null;
}
List<StateInstance> stateInstanceList = queryStateInstanceListByMachineInstanceId(stateMachineInstanceId);
for (StateInstance stateInstance : stateInstanceList) {
stateMachineInstance.putStateInstance(stateInstance.getId(), stateInstance);
}
deserializeParamsAndException(stateMachineInstance);
return stateMachineInstance;
}
@Override
public StateMachineInstance getStateMachineInstanceByBusinessKey(String businessKey, String tenantId) {
if (StringUtils.isEmpty(tenantId)) {
tenantId = defaultTenantId;
}
StateMachineInstance stateMachineInstance = selectOne(
stateLogStoreSqls.getGetStateMachineInstanceByBusinessKeySql(dbType), RESULT_SET_TO_STATE_MACHINE_INSTANCE,
businessKey, tenantId);
if (stateMachineInstance == null) {
return null;
}
List<StateInstance> stateInstanceList = queryStateInstanceListByMachineInstanceId(stateMachineInstance.getId());
for (StateInstance stateInstance : stateInstanceList) {
stateMachineInstance.putStateInstance(stateInstance.getId(), stateInstance);
}
deserializeParamsAndException(stateMachineInstance);
return stateMachineInstance;
}
private void deserializeParamsAndException(StateMachineInstance stateMachineInstance) {
byte[] serializedException = (byte[]) stateMachineInstance.getSerializedException();
if (serializedException != null) {
stateMachineInstance.setException((Exception) exceptionSerializer.deserialize(serializedException));
}
String serializedStartParams = (String) stateMachineInstance.getSerializedStartParams();
if (StringUtils.hasLength(serializedStartParams)) {
stateMachineInstance.setStartParams(
(Map<String, Object>) paramsSerializer.deserialize(serializedStartParams));
}
String serializedEndParams = (String) stateMachineInstance.getSerializedEndParams();
if (StringUtils.hasLength(serializedEndParams)) {
stateMachineInstance.setEndParams((Map<String, Object>) paramsSerializer.deserialize(serializedEndParams));
}
}
@Override
public List<StateMachineInstance> queryStateMachineInstanceByParentId(String parentId) {
return selectList(stateLogStoreSqls.getQueryStateMachineInstancesByParentIdSql(dbType),
RESULT_SET_TO_STATE_MACHINE_INSTANCE, parentId);
}
@Override
public StateInstance getStateInstance(String stateInstanceId, String machineInstId) {
StateInstance stateInstance = selectOne(
stateLogStoreSqls.getGetStateInstanceByIdAndMachineInstanceIdSql(dbType), RESULT_SET_TO_STATE_INSTANCE,
machineInstId, stateInstanceId);
deserializeParamsAndException(stateInstance);
return stateInstance;
}
private void deserializeParamsAndException(StateInstance stateInstance) {
if (stateInstance != null) {
String inputParams = (String) stateInstance.getSerializedInputParams();
if (StringUtils.hasLength(inputParams)) {
stateInstance.setInputParams(paramsSerializer.deserialize(inputParams));
}
String outputParams = (String) stateInstance.getSerializedOutputParams();
if (StringUtils.hasLength(outputParams)) {
stateInstance.setOutputParams(paramsSerializer.deserialize(outputParams));
}
byte[] serializedException = (byte[]) stateInstance.getSerializedException();
if (serializedException != null) {
stateInstance.setException((Exception) exceptionSerializer.deserialize(serializedException));
}
}
}
@Override
public List<StateInstance> queryStateInstanceListByMachineInstanceId(String stateMachineInstanceId) {
List<StateInstance> stateInstanceList = selectList(
stateLogStoreSqls.getQueryStateInstancesByMachineInstanceIdSql(dbType), RESULT_SET_TO_STATE_INSTANCE,
stateMachineInstanceId);
if (CollectionUtils.isEmpty(stateInstanceList)) {
return stateInstanceList;
}
StateInstance lastStateInstance = CollectionUtils.getLast(stateInstanceList);
if (lastStateInstance.getGmtEnd() == null) {
lastStateInstance.setStatus(ExecutionStatus.RU);
}
Map<String, StateInstance> originStateMap = new HashMap<>();
Map<String/* originStateId */, StateInstance/* compensatedState */> compensatedStateMap = new HashMap<>();
Map<String/* originStateId */, StateInstance/* retriedState */> retriedStateMap = new HashMap<>();
for (StateInstance tempStateInstance : stateInstanceList) {
deserializeParamsAndException(tempStateInstance);
if (StringUtils.hasText(tempStateInstance.getStateIdCompensatedFor())) {
putLastStateToMap(compensatedStateMap, tempStateInstance, tempStateInstance.getStateIdCompensatedFor());
} else {
if (StringUtils.hasText(tempStateInstance.getStateIdRetriedFor())) {
putLastStateToMap(retriedStateMap, tempStateInstance, tempStateInstance.getStateIdRetriedFor());
}
originStateMap.put(tempStateInstance.getId(), tempStateInstance);
}
}
if (compensatedStateMap.size() != 0) {
for (StateInstance origState : originStateMap.values()) {
origState.setCompensationState(compensatedStateMap.get(origState.getId()));
}
}
if (retriedStateMap.size() != 0) {
for (StateInstance origState : originStateMap.values()) {
if (retriedStateMap.containsKey(origState.getId())) {
origState.setIgnoreStatus(true);
}
}
}
return stateInstanceList;
}
private void putLastStateToMap(Map<String, StateInstance> resultMap, StateInstance newState, String key) {
if (!resultMap.containsKey(key)) {
resultMap.put(key, newState);
} else if (newState.getGmtEnd().after(resultMap.get(key).getGmtEnd())) {
StateInstance oldState = resultMap.remove(key);
oldState.setIgnoreStatus(true);
resultMap.put(key, newState);
} else {
newState.setIgnoreStatus(true);
}
}
public void setExceptionSerializer(Serializer<Exception, byte[]> exceptionSerializer) {
this.exceptionSerializer = exceptionSerializer;
}
public SagaTransactionalTemplate getSagaTransactionalTemplate() {
return sagaTransactionalTemplate;
}
public void setSagaTransactionalTemplate(SagaTransactionalTemplate sagaTransactionalTemplate) {
this.sagaTransactionalTemplate = sagaTransactionalTemplate;
}
public Serializer<Object, String> getParamsSerializer() {
return paramsSerializer;
}
public void setParamsSerializer(Serializer<Object, String> paramsSerializer) {
this.paramsSerializer = paramsSerializer;
}
public String getDefaultTenantId() {
return defaultTenantId;
}
public void setDefaultTenantId(String defaultTenantId) {
this.defaultTenantId = defaultTenantId;
}
public void setSeqGenerator(SeqGenerator seqGenerator) {
this.seqGenerator = seqGenerator;
}
@Override
public void setTablePrefix(String tablePrefix) {
super.setTablePrefix(tablePrefix);
this.stateLogStoreSqls = new StateLogStoreSqls(tablePrefix);
}
private static class StateMachineInstanceToStatementForInsert implements ObjectToStatement<StateMachineInstance> {
@Override
public void toStatement(StateMachineInstance stateMachineInstance, PreparedStatement statement)
throws SQLException {
statement.setString(1, stateMachineInstance.getId());
statement.setString(2, stateMachineInstance.getMachineId());
statement.setString(3, stateMachineInstance.getTenantId());
statement.setString(4, stateMachineInstance.getParentId());
statement.setTimestamp(5, new Timestamp(stateMachineInstance.getGmtStarted().getTime()));
statement.setString(6, stateMachineInstance.getBusinessKey());
statement.setObject(7, stateMachineInstance.getSerializedStartParams());
statement.setBoolean(8, stateMachineInstance.isRunning());
statement.setString(9, stateMachineInstance.getStatus().name());
statement.setTimestamp(10, new Timestamp(stateMachineInstance.getGmtUpdated().getTime()));
}
}
private static class StateMachineInstanceToStatementForUpdate implements ObjectToStatement<StateMachineInstance> {
@Override
public void toStatement(StateMachineInstance stateMachineInstance, PreparedStatement statement)
throws SQLException {
statement.setTimestamp(1, new Timestamp(stateMachineInstance.getGmtEnd().getTime()));
statement.setBytes(2, stateMachineInstance.getSerializedException() != null ? (byte[]) stateMachineInstance
.getSerializedException() : null);
statement.setObject(3, stateMachineInstance.getSerializedEndParams());
statement.setString(4, stateMachineInstance.getStatus().name());
statement.setString(5,
stateMachineInstance.getCompensationStatus() != null ? stateMachineInstance.getCompensationStatus()
.name() : null);
statement.setBoolean(6, stateMachineInstance.isRunning());
statement.setTimestamp(7, new Timestamp(System.currentTimeMillis()));
statement.setString(8, stateMachineInstance.getId());
statement.setTimestamp(9, new Timestamp(stateMachineInstance.getGmtUpdated().getTime()));
}
}
private static class StateInstanceToStatementForInsert implements ObjectToStatement<StateInstance> {
@Override
public void toStatement(StateInstance stateInstance, PreparedStatement statement) throws SQLException {
statement.setString(1, stateInstance.getId());
statement.setString(2, stateInstance.getMachineInstanceId());
statement.setString(3, stateInstance.getName());
statement.setString(4, stateInstance.getType());
statement.setTimestamp(5, new Timestamp(stateInstance.getGmtStarted().getTime()));
statement.setString(6, stateInstance.getServiceName());
statement.setString(7, stateInstance.getServiceMethod());
statement.setString(8, stateInstance.getServiceType());
statement.setBoolean(9, stateInstance.isForUpdate());
statement.setObject(10, stateInstance.getSerializedInputParams());
statement.setString(11, stateInstance.getStatus().name());
statement.setString(12, stateInstance.getBusinessKey());
statement.setString(13, stateInstance.getStateIdCompensatedFor());
statement.setString(14, stateInstance.getStateIdRetriedFor());
statement.setTimestamp(15, new Timestamp(stateInstance.getGmtUpdated().getTime()));
}
}
private static class StateInstanceToStatementForUpdate implements ObjectToStatement<StateInstance> {
@Override
public void toStatement(StateInstance stateInstance, PreparedStatement statement) throws SQLException {
statement.setTimestamp(1, new Timestamp(stateInstance.getGmtEnd().getTime()));
statement.setBytes(2,
stateInstance.getException() != null ? (byte[]) stateInstance.getSerializedException() : null);
statement.setString(3, stateInstance.getStatus().name());
statement.setObject(4, stateInstance.getSerializedOutputParams());
statement.setTimestamp(5, new Timestamp(stateInstance.getGmtEnd().getTime()));
statement.setString(6, stateInstance.getId());
statement.setString(7, stateInstance.getMachineInstanceId());
}
}
private static class ResultSetToStateMachineInstance implements ResultSetToObject<StateMachineInstance> {
@Override
public StateMachineInstance toObject(ResultSet resultSet) throws SQLException {
StateMachineInstanceImpl stateMachineInstance = new StateMachineInstanceImpl();
stateMachineInstance.setId(resultSet.getString("id"));
stateMachineInstance.setMachineId(resultSet.getString("machine_id"));
stateMachineInstance.setTenantId(resultSet.getString("tenant_id"));
stateMachineInstance.setParentId(resultSet.getString("parent_id"));
stateMachineInstance.setBusinessKey(resultSet.getString("business_key"));
stateMachineInstance.setGmtStarted(resultSet.getTimestamp("gmt_started"));
stateMachineInstance.setGmtEnd(resultSet.getTimestamp("gmt_end"));
stateMachineInstance.setStatus(ExecutionStatus.valueOf(resultSet.getString("status")));
String compensationStatusName = resultSet.getString("compensation_status");
if (StringUtils.hasLength(compensationStatusName)) {
stateMachineInstance.setCompensationStatus(ExecutionStatus.valueOf(compensationStatusName));
}
stateMachineInstance.setRunning(resultSet.getBoolean("is_running"));
stateMachineInstance.setGmtUpdated(resultSet.getTimestamp("gmt_updated"));
if (resultSet.getMetaData().getColumnCount() > 11) {
stateMachineInstance.setSerializedStartParams(resultSet.getString("start_params"));
stateMachineInstance.setSerializedEndParams(resultSet.getString("end_params"));
stateMachineInstance.setSerializedException(resultSet.getBytes("excep"));
}
return stateMachineInstance;
}
}
private static class ResultSetToStateInstance implements ResultSetToObject<StateInstance> {
@Override
public StateInstance toObject(ResultSet resultSet) throws SQLException {
StateInstanceImpl stateInstance = new StateInstanceImpl();
stateInstance.setId(resultSet.getString("id"));
stateInstance.setMachineInstanceId(resultSet.getString("machine_inst_id"));
stateInstance.setName(resultSet.getString("name"));
stateInstance.setType(resultSet.getString("type"));
stateInstance.setBusinessKey(resultSet.getString("business_key"));
stateInstance.setStatus(ExecutionStatus.valueOf(resultSet.getString("status")));
stateInstance.setGmtStarted(resultSet.getTimestamp("gmt_started"));
stateInstance.setGmtEnd(resultSet.getTimestamp("gmt_end"));
stateInstance.setServiceName(resultSet.getString("service_name"));
stateInstance.setServiceMethod(resultSet.getString("service_method"));
stateInstance.setServiceType(resultSet.getString("service_type"));
stateInstance.setForUpdate(resultSet.getBoolean("is_for_update"));
stateInstance.setStateIdCompensatedFor(resultSet.getString("state_id_compensated_for"));
stateInstance.setStateIdRetriedFor(resultSet.getString("state_id_retried_for"));
stateInstance.setSerializedInputParams(resultSet.getString("input_params"));
stateInstance.setSerializedOutputParams(resultSet.getString("output_params"));
stateInstance.setSerializedException(resultSet.getBytes("excep"));
return stateInstance;
}
}
}

View File

@@ -0,0 +1,111 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.store.db;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.List;
import io.seata.common.util.CollectionUtils;
import io.seata.common.util.StringUtils;
import io.seata.saga.engine.store.StateLangStore;
import io.seata.saga.statelang.domain.RecoverStrategy;
import io.seata.saga.statelang.domain.StateMachine;
import io.seata.saga.statelang.domain.StateMachine.Status;
import io.seata.saga.statelang.domain.impl.StateMachineImpl;
/**
* State language definition store in DB
*
* @author lorne.cl
*/
public class DbStateLangStore extends AbstractStore implements StateLangStore {
private static final ResultSetToStateMachine RESULT_SET_TO_STATE_MACHINE = new ResultSetToStateMachine();
private static final StateMachineToStatement STATE_MACHINE_TO_STATEMENT = new StateMachineToStatement();
private StateLangStoreSqls stateLangStoreSqls;
@Override
public StateMachine getStateMachineById(String stateMachineId) {
return selectOne(stateLangStoreSqls.getGetStateMachineByIdSql(dbType), RESULT_SET_TO_STATE_MACHINE,
stateMachineId);
}
@Override
public StateMachine getLastVersionStateMachine(String stateMachineName, String tenantId) {
List<StateMachine> list = selectList(stateLangStoreSqls.getQueryStateMachinesByNameAndTenantSql(dbType),
RESULT_SET_TO_STATE_MACHINE, stateMachineName, tenantId);
if (CollectionUtils.isNotEmpty(list)) {
return list.get(0);
}
return null;
}
@Override
public boolean storeStateMachine(StateMachine stateMachine) {
return executeUpdate(stateLangStoreSqls.getInsertStateMachineSql(dbType), STATE_MACHINE_TO_STATEMENT,
stateMachine) > 0;
}
@Override
public void setTablePrefix(String tablePrefix) {
super.setTablePrefix(tablePrefix);
this.stateLangStoreSqls = new StateLangStoreSqls(tablePrefix);
}
private static class ResultSetToStateMachine implements ResultSetToObject<StateMachine> {
@Override
public StateMachine toObject(ResultSet resultSet) throws SQLException {
StateMachineImpl stateMachine = new StateMachineImpl();
stateMachine.setId(resultSet.getString("id"));
stateMachine.setName(resultSet.getString("name"));
stateMachine.setComment(resultSet.getString("comment_"));
stateMachine.setVersion(resultSet.getString("ver"));
stateMachine.setAppName(resultSet.getString("app_name"));
stateMachine.setContent(resultSet.getString("content"));
stateMachine.setGmtCreate(resultSet.getTimestamp("gmt_create"));
stateMachine.setType(resultSet.getString("type"));
String recoverStrategy = resultSet.getString("recover_strategy");
if (StringUtils.isNotBlank(recoverStrategy)) {
stateMachine.setRecoverStrategy(RecoverStrategy.valueOf(recoverStrategy));
}
stateMachine.setTenantId(resultSet.getString("tenant_id"));
stateMachine.setStatus(Status.valueOf(resultSet.getString("status")));
return stateMachine;
}
}
private static class StateMachineToStatement implements ObjectToStatement<StateMachine> {
@Override
public void toStatement(StateMachine stateMachine, PreparedStatement statement) throws SQLException {
statement.setString(1, stateMachine.getId());
statement.setString(2, stateMachine.getTenantId());
statement.setString(3, stateMachine.getAppName());
statement.setString(4, stateMachine.getName());
statement.setString(5, stateMachine.getStatus().name());
statement.setTimestamp(6, new Timestamp(stateMachine.getGmtCreate().getTime()));
statement.setString(7, stateMachine.getVersion());
statement.setString(8, stateMachine.getType());
statement.setString(9, stateMachine.getContent());
statement.setString(10, stateMachine.getRecoverStrategy() != null ? stateMachine.getRecoverStrategy().name() : null);
statement.setString(11, stateMachine.getComment());
}
}
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.store.db;
/**
* StateLang store sqls
*
* @author lorne.cl
*/
public class StateLangStoreSqls {
private static final String STATE_MACHINE_FIELDS
= "id, tenant_id, app_name, name, status, gmt_create, ver, type, content, recover_strategy, comment_";
private static final String GET_STATE_MACHINE_BY_ID_SQL = "SELECT " + STATE_MACHINE_FIELDS
+ " FROM ${TABLE_PREFIX}state_machine_def WHERE id = ?";
private static final String QUERY_STATE_MACHINES_BY_NAME_AND_TENANT_SQL = "SELECT " + STATE_MACHINE_FIELDS
+ " FROM ${TABLE_PREFIX}state_machine_def WHERE name = ? AND tenant_id = ? ORDER BY gmt_create DESC";
private static final String INSERT_STATE_MACHINE_SQL = "INSERT INTO ${TABLE_PREFIX}state_machine_def ("
+ STATE_MACHINE_FIELDS + ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
private static final String TABLE_PREFIX_REGEX = "\\$\\{TABLE_PREFIX}";
private String tablePrefix;
private String getGetStateMachineByIdSql;
private String queryStateMachinesByNameAndTenantSql;
private String insertStateMachineSql;
public StateLangStoreSqls(String tablePrefix) {
this.tablePrefix = tablePrefix;
init();
}
private void init() {
getGetStateMachineByIdSql = GET_STATE_MACHINE_BY_ID_SQL.replaceAll(TABLE_PREFIX_REGEX, tablePrefix);
queryStateMachinesByNameAndTenantSql = QUERY_STATE_MACHINES_BY_NAME_AND_TENANT_SQL.replaceAll(
TABLE_PREFIX_REGEX, tablePrefix);
insertStateMachineSql = INSERT_STATE_MACHINE_SQL.replaceAll(TABLE_PREFIX_REGEX, tablePrefix);
}
public String getGetStateMachineByIdSql(String dbType) {
return getGetStateMachineByIdSql;
}
public String getQueryStateMachinesByNameAndTenantSql(String dbType) {
return queryStateMachinesByNameAndTenantSql;
}
public String getInsertStateMachineSql(String dbType) {
return insertStateMachineSql;
}
public String getTablePrefix() {
return tablePrefix;
}
}

View File

@@ -0,0 +1,192 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.store.db;
/**
* State log store sqls
*
* @author lorne.cl
*/
public class StateLogStoreSqls {
/**
* machine instance
**/
private static final String STATE_MACHINE_INSTANCE_FIELDS
= "id, machine_id, tenant_id, parent_id, business_key, gmt_started, gmt_end, status, compensation_status, "
+ "is_running, gmt_updated, start_params, end_params, excep";
private static final String STATE_MACHINE_INSTANCE_FIELDS_WITHOUT_PARAMS
= "id, machine_id, tenant_id, parent_id, business_key, gmt_started, gmt_end, status, compensation_status, "
+ "is_running, gmt_updated";
private static final String RECORD_STATE_MACHINE_STARTED_SQL = "INSERT INTO ${TABLE_PREFIX}state_machine_inst\n"
+ "(id, machine_id, tenant_id, parent_id, gmt_started, business_key, start_params, is_running, status, "
+ "gmt_updated)\n"
+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
private static final String RECORD_STATE_MACHINE_FINISHED_SQL
= "UPDATE ${TABLE_PREFIX}state_machine_inst SET gmt_end = ?, excep = ?, end_params = ?,status = ?, "
+ "compensation_status = ?, is_running = ?, gmt_updated = ? WHERE id = ? and gmt_updated = ?";
private static final String UPDATE_STATE_MACHINE_RUNNING_STATUS_SQL =
"UPDATE ${TABLE_PREFIX}state_machine_inst SET\n"
+ "is_running = ?, gmt_updated = ? where id = ? and gmt_updated = ?";
private static final String GET_STATE_MACHINE_INSTANCE_BY_ID_SQL = "SELECT " + STATE_MACHINE_INSTANCE_FIELDS
+ " FROM ${TABLE_PREFIX}state_machine_inst WHERE id = ?";
private static final String GET_STATE_MACHINE_INSTANCE_BY_BUSINESS_KEY_SQL = "SELECT "
+ STATE_MACHINE_INSTANCE_FIELDS
+ " FROM ${TABLE_PREFIX}state_machine_inst WHERE business_key = ? AND tenant_id = ?";
private static final String QUERY_STATE_MACHINE_INSTANCES_BY_PARENT_ID_SQL = "SELECT "
+ STATE_MACHINE_INSTANCE_FIELDS_WITHOUT_PARAMS
+ " FROM ${TABLE_PREFIX}state_machine_inst WHERE parent_id = ? ORDER BY gmt_started DESC";
/**
* state instance
**/
private static final String STATE_INSTANCE_FIELDS
= "id, machine_inst_id, name, type, business_key, gmt_started, service_name, service_method, service_type, "
+ "is_for_update, status, input_params, output_params, excep, gmt_end, state_id_compensated_for, "
+ "state_id_retried_for";
private static final String RECORD_STATE_STARTED_SQL =
"INSERT INTO ${TABLE_PREFIX}state_inst (id, machine_inst_id, name, type,"
+ " gmt_started, service_name, service_method, service_type, is_for_update, input_params, status, "
+ "business_key, state_id_compensated_for, state_id_retried_for, gmt_updated)\n"
+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
private static final String RECORD_STATE_FINISHED_SQL =
"UPDATE ${TABLE_PREFIX}state_inst SET gmt_end = ?, excep = ?, status = ?, output_params = ?, gmt_updated = ? "
+ "WHERE id = ? AND machine_inst_id = ?";
private static final String UPDATE_STATE_EXECUTION_STATUS_SQL
= "UPDATE ${TABLE_PREFIX}state_inst SET status = ?, gmt_updated = ? WHERE machine_inst_id = ? AND id = ?";
private static final String QUERY_STATE_INSTANCES_BY_MACHINE_INSTANCE_ID_SQL = "SELECT " + STATE_INSTANCE_FIELDS
+ " FROM ${TABLE_PREFIX}state_inst WHERE machine_inst_id = ? ORDER BY gmt_started, ID ASC";
private static final String GET_STATE_INSTANCE_BY_ID_AND_MACHINE_INSTANCE_ID_SQL = "SELECT " + STATE_INSTANCE_FIELDS
+ " FROM ${TABLE_PREFIX}state_inst WHERE machine_inst_id = ? AND id = ?";
private static final String TABLE_PREFIX_REGEX = "\\$\\{TABLE_PREFIX}";
private String tablePrefix;
/**
* machine instance
**/
private String recordStateMachineStartedSql;
private String recordStateMachineFinishedSql;
private String updateStateMachineRunningStatusSql;
private String getStateMachineInstanceByIdSql;
private String getStateMachineInstanceByBusinessKeySql;
private String queryStateMachineInstancesByParentIdSql;
/**
* state instance
**/
private String recordStateStartedSql;
private String recordStateFinishedSql;
private String updateStateExecutionStatusSql;
private String queryStateInstancesByMachineInstanceIdSql;
private String getStateInstanceByIdAndMachineInstanceIdSql;
public StateLogStoreSqls(String tablePrefix) {
this.tablePrefix = tablePrefix;
init();
}
private void init() {
recordStateMachineStartedSql = RECORD_STATE_MACHINE_STARTED_SQL.replaceAll(TABLE_PREFIX_REGEX, tablePrefix);
recordStateMachineFinishedSql = RECORD_STATE_MACHINE_FINISHED_SQL.replaceAll(TABLE_PREFIX_REGEX, tablePrefix);
updateStateMachineRunningStatusSql = UPDATE_STATE_MACHINE_RUNNING_STATUS_SQL.replaceAll(TABLE_PREFIX_REGEX,
tablePrefix);
getStateMachineInstanceByIdSql = GET_STATE_MACHINE_INSTANCE_BY_ID_SQL.replaceAll(TABLE_PREFIX_REGEX,
tablePrefix);
getStateMachineInstanceByBusinessKeySql = GET_STATE_MACHINE_INSTANCE_BY_BUSINESS_KEY_SQL.replaceAll(
TABLE_PREFIX_REGEX, tablePrefix);
queryStateMachineInstancesByParentIdSql = QUERY_STATE_MACHINE_INSTANCES_BY_PARENT_ID_SQL.replaceAll(
TABLE_PREFIX_REGEX, tablePrefix);
recordStateStartedSql = RECORD_STATE_STARTED_SQL.replaceAll(TABLE_PREFIX_REGEX, tablePrefix);
recordStateFinishedSql = RECORD_STATE_FINISHED_SQL.replaceAll(TABLE_PREFIX_REGEX, tablePrefix);
updateStateExecutionStatusSql = UPDATE_STATE_EXECUTION_STATUS_SQL.replaceAll(TABLE_PREFIX_REGEX, tablePrefix);
queryStateInstancesByMachineInstanceIdSql = QUERY_STATE_INSTANCES_BY_MACHINE_INSTANCE_ID_SQL.replaceAll(
TABLE_PREFIX_REGEX, tablePrefix);
getStateInstanceByIdAndMachineInstanceIdSql = GET_STATE_INSTANCE_BY_ID_AND_MACHINE_INSTANCE_ID_SQL.replaceAll(
TABLE_PREFIX_REGEX, tablePrefix);
}
public String getRecordStateMachineStartedSql(String dbType) {
return recordStateMachineStartedSql;
}
public String getRecordStateMachineFinishedSql(String dbType) {
return recordStateMachineFinishedSql;
}
public String getUpdateStateMachineRunningStatusSql(String dbType) {
return updateStateMachineRunningStatusSql;
}
public String getGetStateMachineInstanceByIdSql(String dbType) {
return getStateMachineInstanceByIdSql;
}
public String getGetStateMachineInstanceByBusinessKeySql(String dbType) {
return getStateMachineInstanceByBusinessKeySql;
}
public String getQueryStateMachineInstancesByParentIdSql(String dbType) {
return queryStateMachineInstancesByParentIdSql;
}
public String getRecordStateStartedSql(String dbType) {
return recordStateStartedSql;
}
public String getRecordStateFinishedSql(String dbType) {
return recordStateFinishedSql;
}
public String getUpdateStateExecutionStatusSql(String dbType) {
return updateStateExecutionStatusSql;
}
public String getQueryStateInstancesByMachineInstanceIdSql(String dbType) {
return queryStateInstancesByMachineInstanceIdSql;
}
public String getGetStateInstanceByIdAndMachineInstanceIdSql(String dbType) {
return getStateInstanceByIdAndMachineInstanceIdSql;
}
public String getTablePrefix() {
return tablePrefix;
}
}

View File

@@ -0,0 +1 @@
io.seata.saga.engine.pcext.interceptors.InSagaBranchHandlerInterceptor

View File

@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 1999-2019 Seata.io Group.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>seata-saga</artifactId>
<groupId>io.seata</groupId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<name>seata-saga-engine ${project.version}</name>
<artifactId>seata-saga-engine</artifactId>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>seata-saga-statelang</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>seata-saga-processctrl</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,44 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine;
import io.seata.saga.proctrl.ProcessContext;
import io.seata.saga.statelang.domain.StateMachineInstance;
/**
* Async Callback
*
* @author lorne.cl
*/
public interface AsyncCallback {
/**
* on finished
*
* @param context
* @param stateMachineInstance
*/
void onFinished(ProcessContext context, StateMachineInstance stateMachineInstance);
/**
* on error
*
* @param context
* @param stateMachineInstance
* @param exp
*/
void onError(ProcessContext context, StateMachineInstance stateMachineInstance, Exception exp);
}

View File

@@ -0,0 +1,171 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine;
import java.util.concurrent.ThreadPoolExecutor;
import io.seata.saga.engine.evaluation.EvaluatorFactoryManager;
import io.seata.saga.engine.expression.ExpressionFactoryManager;
import io.seata.saga.engine.invoker.ServiceInvokerManager;
import io.seata.saga.engine.repo.StateLogRepository;
import io.seata.saga.engine.repo.StateMachineRepository;
import io.seata.saga.engine.sequence.SeqGenerator;
import io.seata.saga.engine.store.StateLangStore;
import io.seata.saga.engine.store.StateLogStore;
import io.seata.saga.engine.strategy.StatusDecisionStrategy;
import io.seata.saga.proctrl.eventing.impl.ProcessCtrlEventPublisher;
import org.springframework.context.ApplicationContext;
import javax.script.ScriptEngineManager;
/**
* StateMachineConfig
*
* @author lorne.cl
*/
public interface StateMachineConfig {
/**
* Gets state log store.
*
* @return the StateLogRepository
*/
StateLogRepository getStateLogRepository();
/**
* Gets get state log store.
*
* @return the get StateLogStore
*/
StateLogStore getStateLogStore();
/**
* Gets get state language definition store.
*
* @return the get StateLangStore
*/
StateLangStore getStateLangStore();
/**
* Gets get expression factory manager.
*
* @return the get expression factory manager
*/
ExpressionFactoryManager getExpressionFactoryManager();
/**
* Gets get evaluator factory manager.
*
* @return the get evaluator factory manager
*/
EvaluatorFactoryManager getEvaluatorFactoryManager();
/**
* Gets get charset.
*
* @return the get charset
*/
String getCharset();
/**
* Gets get default tenant id.
*
* @return the default tenant id
*/
String getDefaultTenantId();
/**
* Gets get state machine repository.
*
* @return the get state machine repository
*/
StateMachineRepository getStateMachineRepository();
/**
* Gets get status decision strategy.
*
* @return the get status decision strategy
*/
StatusDecisionStrategy getStatusDecisionStrategy();
/**
* Gets get seq generator.
*
* @return the get seq generator
*/
SeqGenerator getSeqGenerator();
/**
* Gets get process ctrl event publisher.
*
* @return the get process ctrl event publisher
*/
ProcessCtrlEventPublisher getProcessCtrlEventPublisher();
/**
* Gets get async process ctrl event publisher.
*
* @return the get async process ctrl event publisher
*/
ProcessCtrlEventPublisher getAsyncProcessCtrlEventPublisher();
/**
* Gets get application context.
*
* @return the get application context
*/
ApplicationContext getApplicationContext();
/**
* Gets get thread pool executor.
*
* @return the get thread pool executor
*/
ThreadPoolExecutor getThreadPoolExecutor();
/**
* Is enable async boolean.
*
* @return the boolean
*/
boolean isEnableAsync();
/**
* get ServiceInvokerManager
*
* @return
*/
ServiceInvokerManager getServiceInvokerManager();
/**
* get trans operation timeout
* @return
*/
int getTransOperationTimeout();
/**
* get service invoke timeout
* @return
*/
int getServiceInvokeTimeout();
/**
* get ScriptEngineManager
*
* @return
*/
ScriptEngineManager getScriptEngineManager();
}

View File

@@ -0,0 +1,163 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine;
import java.util.Map;
import io.seata.saga.engine.exception.EngineExecutionException;
import io.seata.saga.engine.exception.ForwardInvalidException;
import io.seata.saga.statelang.domain.StateMachineInstance;
/**
* State machine engine
*
* @author lorne.cl
*/
public interface StateMachineEngine {
/**
* start a state machine instance
*
* @param stateMachineName
* @param tenantId
* @param startParams
* @return
* @throws EngineExecutionException
*/
StateMachineInstance start(String stateMachineName, String tenantId, Map<String, Object> startParams)
throws EngineExecutionException;
/**
* start a state machine instance with businessKey
*
* @param stateMachineName
* @param tenantId
* @param businessKey
* @param startParams
* @return
* @throws EngineExecutionException
*/
StateMachineInstance startWithBusinessKey(String stateMachineName, String tenantId, String businessKey,
Map<String, Object> startParams) throws EngineExecutionException;
/**
* start a state machine instance asynchronously
*
* @param stateMachineName
* @param tenantId
* @param startParams
* @param callback
* @return
* @throws EngineExecutionException
*/
StateMachineInstance startAsync(String stateMachineName, String tenantId, Map<String, Object> startParams,
AsyncCallback callback) throws EngineExecutionException;
/**
* start a state machine instance asynchronously with businessKey
*
* @param stateMachineName
* @param tenantId
* @param businessKey
* @param startParams
* @param callback
* @return
* @throws EngineExecutionException
*/
StateMachineInstance startWithBusinessKeyAsync(String stateMachineName, String tenantId, String businessKey,
Map<String, Object> startParams, AsyncCallback callback)
throws EngineExecutionException;
/**
* forward restart a failed state machine instance
*
* @param stateMachineInstId
* @param replaceParams
* @return
* @throws ForwardInvalidException
*/
StateMachineInstance forward(String stateMachineInstId, Map<String, Object> replaceParams)
throws ForwardInvalidException;
/**
* forward restart a failed state machine instance asynchronously
*
* @param stateMachineInstId
* @param replaceParams
* @param callback
* @return
* @throws ForwardInvalidException
*/
StateMachineInstance forwardAsync(String stateMachineInstId, Map<String, Object> replaceParams,
AsyncCallback callback) throws ForwardInvalidException;
/**
* compensate a state machine instance
*
* @param stateMachineInstId
* @param replaceParams
* @return
* @throws EngineExecutionException
*/
StateMachineInstance compensate(String stateMachineInstId, Map<String, Object> replaceParams)
throws EngineExecutionException;
/**
* compensate a state machine instance asynchronously
*
* @param stateMachineInstId
* @param replaceParams
* @param callback
* @return
* @throws EngineExecutionException
*/
StateMachineInstance compensateAsync(String stateMachineInstId, Map<String, Object> replaceParams,
AsyncCallback callback) throws EngineExecutionException;
/**
* skip current failed state instance and forward restart state machine instance
*
* @param stateMachineInstId
* @return
* @throws EngineExecutionException
*/
StateMachineInstance skipAndForward(String stateMachineInstId, Map<String, Object> replaceParams) throws EngineExecutionException;
/**
* skip current failed state instance and forward restart state machine instance asynchronously
*
* @param stateMachineInstId
* @param callback
* @return
* @throws EngineExecutionException
*/
StateMachineInstance skipAndForwardAsync(String stateMachineInstId, AsyncCallback callback)
throws EngineExecutionException;
/**
* get state machine configurations
*
* @return
*/
StateMachineConfig getStateMachineConfig();
/**
* Reload StateMachine Instance
* @param instId
* @return
*/
StateMachineInstance reloadStateMachineInstance(String instId);
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.evaluation;
import java.util.Map;
/**
* Evaluator
*
* @author lorne.cl
*/
public interface Evaluator {
boolean evaluate(Map<String, Object> variables);
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.evaluation;
/**
* Evaluator Factory
*
* @author lorne.cl
* @see Evaluator
*/
public interface EvaluatorFactory {
/**
* create evaluator
*
* @param expressionString
* @return
*/
Evaluator createEvaluator(String expressionString);
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.evaluation;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import io.seata.common.util.StringUtils;
/**
* Evaluator Factory Manager
*
* @author lorne.cl
* @see EvaluatorFactory
* @see Evaluator
*/
public class EvaluatorFactoryManager {
public static final String EVALUATOR_TYPE_DEFAULT = "Default";
private Map<String, EvaluatorFactory> evaluatorFactoryMap = new ConcurrentHashMap<>();
public EvaluatorFactory getEvaluatorFactory(String type) {
if (StringUtils.isBlank(type)) {
type = EVALUATOR_TYPE_DEFAULT;
}
return this.evaluatorFactoryMap.get(type);
}
public Map<String, EvaluatorFactory> getEvaluatorFactoryMap() {
return evaluatorFactoryMap;
}
public void setEvaluatorFactoryMap(Map<String, EvaluatorFactory> evaluatorFactoryMap) {
this.evaluatorFactoryMap.putAll(evaluatorFactoryMap);
}
public void putEvaluatorFactory(String type, EvaluatorFactory factory) {
this.evaluatorFactoryMap.put(type, factory);
}
}

View File

@@ -0,0 +1,98 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.evaluation.exception;
import java.util.Map;
import io.seata.common.exception.FrameworkErrorCode;
import io.seata.saga.engine.evaluation.Evaluator;
import io.seata.saga.engine.exception.EngineExecutionException;
import io.seata.saga.statelang.domain.DomainConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
/**
* Exception match evaluator
*
* @author lorne.cl
*/
public class ExceptionMatchEvaluator implements Evaluator {
private static final Logger LOGGER = LoggerFactory.getLogger(ExceptionMatchEvaluator.class);
private String exceptionString;
private Class<Exception> exceptionClass;
private String rootObjectName = DomainConstants.VAR_NAME_CURRENT_EXCEPTION;
@Override
public boolean evaluate(Map<String, Object> variables) {
Object eObj = variables.get(getRootObjectName());
if (eObj != null && (eObj instanceof Exception) && StringUtils.hasText(exceptionString)) {
Exception e = (Exception)eObj;
String exceptionClassName = e.getClass().getName();
if (exceptionClassName.equals(exceptionString)) {
return true;
}
try {
if (exceptionClass.isAssignableFrom(e.getClass())) {
return true;
}
} catch (Exception e1) {
LOGGER.error("Exception Match failed. expression[{}]", exceptionString, e1);
}
}
return false;
}
public String getExceptionString() {
return exceptionString;
}
@SuppressWarnings("unchecked")
public void setExceptionString(String exceptionString) {
this.exceptionString = exceptionString;
try {
this.exceptionClass = (Class<Exception>)Class.forName(exceptionString);
} catch (ClassNotFoundException e) {
throw new EngineExecutionException(e, exceptionString + " is not a Exception Class",
FrameworkErrorCode.NotExceptionClass);
}
}
public Class<Exception> getExceptionClass() {
return exceptionClass;
}
public void setExceptionClass(Class<Exception> exceptionClass) {
this.exceptionClass = exceptionClass;
this.exceptionString = exceptionClass.getName();
}
public String getRootObjectName() {
return rootObjectName;
}
public void setRootObjectName(String rootObjectName) {
this.rootObjectName = rootObjectName;
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.evaluation.exception;
import io.seata.saga.engine.evaluation.Evaluator;
import io.seata.saga.engine.evaluation.EvaluatorFactory;
import io.seata.saga.statelang.domain.DomainConstants;
/**
* Exception match evaluator factory
*
* @author lorne.cl
*/
public class ExceptionMatchEvaluatorFactory implements EvaluatorFactory {
@Override
public Evaluator createEvaluator(String expressionString) {
ExceptionMatchEvaluator evaluator = new ExceptionMatchEvaluator();
evaluator.setExceptionString(expressionString);
evaluator.setRootObjectName(DomainConstants.VAR_NAME_CURRENT_EXCEPTION);
return evaluator;
}
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.evaluation.expression;
import java.util.Map;
import io.seata.common.exception.FrameworkErrorCode;
import io.seata.saga.engine.evaluation.Evaluator;
import io.seata.saga.engine.exception.EngineExecutionException;
import io.seata.saga.engine.expression.Expression;
import io.seata.saga.statelang.domain.DomainConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
/**
* Expression evaluator
*
* @author lorne.cl
*/
public class ExpressionEvaluator implements Evaluator {
private static final Logger LOGGER = LoggerFactory.getLogger(ExpressionEvaluator.class);
private Expression expression;
/**
* If it is empty, use variables as the root variable, otherwise take rootObjectName as the root.
*/
private String rootObjectName = DomainConstants.VAR_NAME_STATEMACHINE_CONTEXT;
@Override
public boolean evaluate(Map<String, Object> variables) {
Object rootObject;
if (StringUtils.hasText(this.rootObjectName)) {
rootObject = variables.get(this.rootObjectName);
} else {
rootObject = variables;
}
Object result;
try {
result = expression.getValue(rootObject);
} catch (Exception e) {
result = Boolean.FALSE;
if (LOGGER.isWarnEnabled()) {
LOGGER.warn("Expression [{}] execute failed, and it will return false by default. variables:{}",
expression.getExpressionString(), variables, e);
}
}
if (result == null) {
throw new EngineExecutionException("Evaluation returns null", FrameworkErrorCode.EvaluationReturnsNull);
}
if (!(result instanceof Boolean)) {
throw new EngineExecutionException(
"Evaluation returns non-Boolean: " + result + " (" + result.getClass().getName() + ")",
FrameworkErrorCode.EvaluationReturnsNonBoolean);
}
return (Boolean)result;
}
public Expression getExpression() {
return expression;
}
public void setExpression(Expression expression) {
this.expression = expression;
}
public String getRootObjectName() {
return rootObjectName;
}
public void setRootObjectName(String rootObjectName) {
this.rootObjectName = rootObjectName;
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.evaluation.expression;
import io.seata.saga.engine.evaluation.Evaluator;
import io.seata.saga.engine.evaluation.EvaluatorFactory;
import io.seata.saga.engine.expression.ExpressionFactory;
/**
* Expression evaluator factory
*
* @author lorne.cl
*/
public class ExpressionEvaluatorFactory implements EvaluatorFactory {
private ExpressionFactory expressionFactory;
@Override
public Evaluator createEvaluator(String expressionString) {
ExpressionEvaluator evaluator = new ExpressionEvaluator();
evaluator.setExpression(this.expressionFactory.createExpression(expressionString));
return evaluator;
}
public ExpressionFactory getExpressionFactory() {
return expressionFactory;
}
public void setExpressionFactory(ExpressionFactory expressionFactory) {
this.expressionFactory = expressionFactory;
}
}

View File

@@ -0,0 +1,91 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.exception;
import io.seata.common.exception.FrameworkErrorCode;
import io.seata.common.exception.FrameworkException;
/**
* StateMachineEngine Execution Exception
*
* @author lorne.cl
*/
public class EngineExecutionException extends FrameworkException {
private String stateName;
private String stateMachineName;
private String stateMachineInstanceId;
private String stateInstanceId;
public EngineExecutionException() {
}
public EngineExecutionException(FrameworkErrorCode err) {
super(err);
}
public EngineExecutionException(String msg) {
super(msg);
}
public EngineExecutionException(String msg, FrameworkErrorCode errCode) {
super(msg, errCode);
}
public EngineExecutionException(Throwable cause, String msg, FrameworkErrorCode errCode) {
super(cause, msg, errCode);
}
public EngineExecutionException(Throwable th) {
super(th);
}
public EngineExecutionException(Throwable th, String msg) {
super(th, msg);
}
public String getStateName() {
return stateName;
}
public void setStateName(String stateName) {
this.stateName = stateName;
}
public String getStateMachineName() {
return stateMachineName;
}
public void setStateMachineName(String stateMachineName) {
this.stateMachineName = stateMachineName;
}
public String getStateMachineInstanceId() {
return stateMachineInstanceId;
}
public void setStateMachineInstanceId(String stateMachineInstanceId) {
this.stateMachineInstanceId = stateMachineInstanceId;
}
public String getStateInstanceId() {
return stateInstanceId;
}
public void setStateInstanceId(String stateInstanceId) {
this.stateInstanceId = stateInstanceId;
}
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.exception;
import io.seata.common.exception.FrameworkErrorCode;
/**
* Forward operation invalid exception
*
* @author lorne.cl
*/
public class ForwardInvalidException extends EngineExecutionException {
public ForwardInvalidException() {
}
public ForwardInvalidException(FrameworkErrorCode err) {
super(err);
}
public ForwardInvalidException(String msg) {
super(msg);
}
public ForwardInvalidException(String msg, FrameworkErrorCode errCode) {
super(msg, errCode);
}
public ForwardInvalidException(Throwable cause, String msg, FrameworkErrorCode errCode) {
super(cause, msg, errCode);
}
public ForwardInvalidException(Throwable th) {
super(th);
}
public ForwardInvalidException(Throwable th, String msg) {
super(th, msg);
}
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.expression;
/**
* Expression
*
* @author lorne.cl
*/
public interface Expression {
/**
* Gets get value.
*
* @param elContext the el context
* @return the get value
*/
Object getValue(Object elContext);
/**
* Sets set value.
*
* @param value the value
* @param elContext the el context
*/
void setValue(Object value, Object elContext);
/**
* Gets get expression string.
*
* @return the get expression string
*/
String getExpressionString();
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.expression;
/**
* Expression Factory
*
* @author lorne.cl
*/
public interface ExpressionFactory {
/**
* create expression
*
* @param expression
* @return
*/
Expression createExpression(String expression);
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.expression;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import io.seata.common.util.StringUtils;
/**
* Expression factory manager
*
* @author lorne.cl
*/
public class ExpressionFactoryManager {
public static final String DEFAULT_EXPRESSION_TYPE = "Default";
private Map<String, ExpressionFactory> expressionFactoryMap = new ConcurrentHashMap<>();
public ExpressionFactory getExpressionFactory(String expressionType) {
if (StringUtils.isBlank(expressionType)) {
expressionType = DEFAULT_EXPRESSION_TYPE;
}
return expressionFactoryMap.get(expressionType);
}
public void setExpressionFactoryMap(Map<String, ExpressionFactory> expressionFactoryMap) {
this.expressionFactoryMap.putAll(expressionFactoryMap);
}
public void putExpressionFactory(String type, ExpressionFactory factory) {
this.expressionFactoryMap.put(type, factory);
}
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.expression.seq;
import io.seata.saga.engine.expression.Expression;
import io.seata.saga.engine.sequence.SeqGenerator;
/**
* Generate sequence expression
*
* @author lorne.cl
*/
public class SequenceExpression implements Expression {
private SeqGenerator seqGenerator;
private String entity;
private String rule;
@Override
public Object getValue(Object elContext) {
return seqGenerator.generate(entity, rule, null);
}
@Override
public void setValue(Object value, Object elContext) {
}
@Override
public String getExpressionString() {
return this.entity + "|" + this.rule;
}
public SeqGenerator getSeqGenerator() {
return seqGenerator;
}
public void setSeqGenerator(SeqGenerator seqGenerator) {
this.seqGenerator = seqGenerator;
}
public String getEntity() {
return entity;
}
public void setEntity(String entity) {
this.entity = entity;
}
public String getRule() {
return rule;
}
public void setRule(String rule) {
this.rule = rule;
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.expression.seq;
import io.seata.saga.engine.expression.Expression;
import io.seata.saga.engine.expression.ExpressionFactory;
import io.seata.saga.engine.sequence.SeqGenerator;
import org.springframework.util.StringUtils;
/**
* Sequence expression factory
*
* @author lorne.cl
*/
public class SequenceExpressionFactory implements ExpressionFactory {
private SeqGenerator seqGenerator;
@Override
public Expression createExpression(String expressionString) {
SequenceExpression sequenceExpression = new SequenceExpression();
sequenceExpression.setSeqGenerator(this.seqGenerator);
if (StringUtils.hasLength(expressionString)) {
String[] strings = expressionString.split("\\|");
if (strings.length >= 2) {
sequenceExpression.setEntity(strings[0]);
sequenceExpression.setRule(strings[1]);
}
}
return sequenceExpression;
}
public SeqGenerator getSeqGenerator() {
return seqGenerator;
}
public void setSeqGenerator(SeqGenerator seqGenerator) {
this.seqGenerator = seqGenerator;
}
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.expression.spel;
import io.seata.saga.engine.expression.Expression;
/**
* Expression base on Spring EL
*
* @author lorne.cl
*/
public class SpringELExpression implements Expression {
private org.springframework.expression.Expression expression;
public SpringELExpression(org.springframework.expression.Expression expression) {
this.expression = expression;
}
@Override
public Object getValue(Object elContext) {
return expression.getValue(elContext);
}
@Override
public void setValue(Object value, Object elContext) {
expression.setValue(elContext, value);
}
@Override
public String getExpressionString() {
return expression.getExpressionString();
}
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.expression.spel;
import io.seata.saga.engine.expression.Expression;
import io.seata.saga.engine.expression.ExpressionFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.expression.AccessException;
import org.springframework.expression.BeanResolver;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
/**
* SpringELExpression factory
*
* @author lorne.cl
*/
public class SpringELExpressionFactory implements ExpressionFactory, ApplicationContextAware {
ExpressionParser parser = new SpelExpressionParser();
ApplicationContext applicationContext;
@Override
public Expression createExpression(String expression) {
org.springframework.expression.Expression defaultExpression = parser.parseExpression(expression);
EvaluationContext evaluationContext = ((SpelExpression)defaultExpression).getEvaluationContext();
((StandardEvaluationContext)evaluationContext).setBeanResolver(new AppContextBeanResolver());
return new SpringELExpression(defaultExpression);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
private class AppContextBeanResolver implements BeanResolver {
@Override
public Object resolve(EvaluationContext context, String beanName) throws AccessException {
return applicationContext.getBean(beanName);
}
}
}

View File

@@ -0,0 +1,501 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.impl;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ThreadPoolExecutor;
import io.seata.common.loader.EnhancedServiceLoader;
import io.seata.saga.engine.StateMachineConfig;
import io.seata.saga.engine.evaluation.EvaluatorFactoryManager;
import io.seata.saga.engine.evaluation.exception.ExceptionMatchEvaluatorFactory;
import io.seata.saga.engine.evaluation.expression.ExpressionEvaluatorFactory;
import io.seata.saga.engine.expression.ExpressionFactoryManager;
import io.seata.saga.engine.expression.seq.SequenceExpressionFactory;
import io.seata.saga.engine.expression.spel.SpringELExpressionFactory;
import io.seata.saga.engine.invoker.ServiceInvokerManager;
import io.seata.saga.engine.invoker.impl.SpringBeanServiceInvoker;
import io.seata.saga.engine.pcext.InterceptableStateHandler;
import io.seata.saga.engine.pcext.InterceptableStateRouter;
import io.seata.saga.engine.pcext.StateHandler;
import io.seata.saga.engine.pcext.StateHandlerInterceptor;
import io.seata.saga.engine.pcext.StateMachineProcessHandler;
import io.seata.saga.engine.pcext.StateMachineProcessRouter;
import io.seata.saga.engine.pcext.StateRouter;
import io.seata.saga.engine.pcext.StateRouterInterceptor;
import io.seata.saga.engine.repo.StateLogRepository;
import io.seata.saga.engine.repo.StateMachineRepository;
import io.seata.saga.engine.repo.impl.StateLogRepositoryImpl;
import io.seata.saga.engine.repo.impl.StateMachineRepositoryImpl;
import io.seata.saga.engine.sequence.SeqGenerator;
import io.seata.saga.engine.sequence.SpringJvmUUIDSeqGenerator;
import io.seata.saga.engine.store.StateLangStore;
import io.seata.saga.engine.store.StateLogStore;
import io.seata.saga.engine.strategy.StatusDecisionStrategy;
import io.seata.saga.engine.strategy.impl.DefaultStatusDecisionStrategy;
import io.seata.saga.proctrl.ProcessRouter;
import io.seata.saga.proctrl.ProcessType;
import io.seata.saga.proctrl.eventing.impl.AsyncEventBus;
import io.seata.saga.proctrl.eventing.impl.DirectEventBus;
import io.seata.saga.proctrl.eventing.impl.ProcessCtrlEventConsumer;
import io.seata.saga.proctrl.eventing.impl.ProcessCtrlEventPublisher;
import io.seata.saga.proctrl.handler.DefaultRouterHandler;
import io.seata.saga.proctrl.handler.ProcessHandler;
import io.seata.saga.proctrl.handler.RouterHandler;
import io.seata.saga.proctrl.impl.ProcessControllerImpl;
import io.seata.saga.proctrl.process.impl.CustomizeBusinessProcessor;
import io.seata.saga.statelang.domain.DomainConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.io.Resource;
import javax.script.ScriptEngineManager;
import static io.seata.common.DefaultValues.DEFAULT_CLIENT_SAGA_COMPENSATE_PERSIST_MODE_UPDATE;
import static io.seata.common.DefaultValues.DEFAULT_CLIENT_SAGA_RETRY_PERSIST_MODE_UPDATE;
import static io.seata.common.DefaultValues.DEFAULT_SAGA_JSON_PARSER;
/**
* Default state machine configuration
*
* @author lorne.cl
*/
public class DefaultStateMachineConfig implements StateMachineConfig, ApplicationContextAware, InitializingBean {
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultStateMachineConfig.class);
private static final int DEFAULT_TRANS_OPER_TIMEOUT = 60000 * 30;
private static final int DEFAULT_SERVICE_INVOKE_TIMEOUT = 60000 * 5;
private int transOperationTimeout = DEFAULT_TRANS_OPER_TIMEOUT;
private int serviceInvokeTimeout = DEFAULT_SERVICE_INVOKE_TIMEOUT;
private StateLogRepository stateLogRepository;
private StateLogStore stateLogStore;
private StateLangStore stateLangStore;
private ExpressionFactoryManager expressionFactoryManager;
private EvaluatorFactoryManager evaluatorFactoryManager;
private StateMachineRepository stateMachineRepository;
private StatusDecisionStrategy statusDecisionStrategy;
private SeqGenerator seqGenerator;
private ProcessCtrlEventPublisher syncProcessCtrlEventPublisher;
private ProcessCtrlEventPublisher asyncProcessCtrlEventPublisher;
private ApplicationContext applicationContext;
private ThreadPoolExecutor threadPoolExecutor;
private boolean enableAsync;
private ServiceInvokerManager serviceInvokerManager;
private Resource[] resources = new Resource[0];
private String charset = "UTF-8";
private String defaultTenantId = "000001";
private ScriptEngineManager scriptEngineManager;
private String sagaJsonParser = DEFAULT_SAGA_JSON_PARSER;
private boolean sagaRetryPersistModeUpdate = DEFAULT_CLIENT_SAGA_RETRY_PERSIST_MODE_UPDATE;
private boolean sagaCompensatePersistModeUpdate = DEFAULT_CLIENT_SAGA_COMPENSATE_PERSIST_MODE_UPDATE;
protected void init() throws Exception {
if (expressionFactoryManager == null) {
expressionFactoryManager = new ExpressionFactoryManager();
SpringELExpressionFactory springELExpressionFactory = new SpringELExpressionFactory();
springELExpressionFactory.setApplicationContext(getApplicationContext());
expressionFactoryManager.putExpressionFactory(ExpressionFactoryManager.DEFAULT_EXPRESSION_TYPE,
springELExpressionFactory);
SequenceExpressionFactory sequenceExpressionFactory = new SequenceExpressionFactory();
sequenceExpressionFactory.setSeqGenerator(getSeqGenerator());
expressionFactoryManager.putExpressionFactory(DomainConstants.EXPRESSION_TYPE_SEQUENCE,
sequenceExpressionFactory);
}
if (evaluatorFactoryManager == null) {
evaluatorFactoryManager = new EvaluatorFactoryManager();
ExpressionEvaluatorFactory expressionEvaluatorFactory = new ExpressionEvaluatorFactory();
expressionEvaluatorFactory.setExpressionFactory(
expressionFactoryManager.getExpressionFactory(ExpressionFactoryManager.DEFAULT_EXPRESSION_TYPE));
evaluatorFactoryManager.putEvaluatorFactory(EvaluatorFactoryManager.EVALUATOR_TYPE_DEFAULT,
expressionEvaluatorFactory);
evaluatorFactoryManager.putEvaluatorFactory(DomainConstants.EVALUATOR_TYPE_EXCEPTION,
new ExceptionMatchEvaluatorFactory());
}
if (stateMachineRepository == null) {
StateMachineRepositoryImpl stateMachineRepository = new StateMachineRepositoryImpl();
stateMachineRepository.setCharset(charset);
stateMachineRepository.setSeqGenerator(seqGenerator);
stateMachineRepository.setStateLangStore(stateLangStore);
stateMachineRepository.setDefaultTenantId(defaultTenantId);
stateMachineRepository.setJsonParserName(sagaJsonParser);
if (resources != null) {
try {
stateMachineRepository.registryByResources(resources, defaultTenantId);
} catch (IOException e) {
LOGGER.error("Load State Language Resources failed.", e);
}
}
this.stateMachineRepository = stateMachineRepository;
}
if (stateLogRepository == null) {
StateLogRepositoryImpl stateLogRepositoryImpl = new StateLogRepositoryImpl();
stateLogRepositoryImpl.setStateLogStore(stateLogStore);
this.stateLogRepository = stateLogRepositoryImpl;
}
if (statusDecisionStrategy == null) {
statusDecisionStrategy = new DefaultStatusDecisionStrategy();
}
if (syncProcessCtrlEventPublisher == null) {
ProcessCtrlEventPublisher syncEventPublisher = new ProcessCtrlEventPublisher();
ProcessControllerImpl processorController = createProcessorController(syncEventPublisher);
ProcessCtrlEventConsumer processCtrlEventConsumer = new ProcessCtrlEventConsumer();
processCtrlEventConsumer.setProcessController(processorController);
DirectEventBus directEventBus = new DirectEventBus();
syncEventPublisher.setEventBus(directEventBus);
directEventBus.registerEventConsumer(processCtrlEventConsumer);
syncProcessCtrlEventPublisher = syncEventPublisher;
}
if (enableAsync && asyncProcessCtrlEventPublisher == null) {
ProcessCtrlEventPublisher asyncEventPublisher = new ProcessCtrlEventPublisher();
ProcessControllerImpl processorController = createProcessorController(asyncEventPublisher);
ProcessCtrlEventConsumer processCtrlEventConsumer = new ProcessCtrlEventConsumer();
processCtrlEventConsumer.setProcessController(processorController);
AsyncEventBus asyncEventBus = new AsyncEventBus();
asyncEventBus.setThreadPoolExecutor(getThreadPoolExecutor());
asyncEventPublisher.setEventBus(asyncEventBus);
asyncEventBus.registerEventConsumer(processCtrlEventConsumer);
asyncProcessCtrlEventPublisher = asyncEventPublisher;
}
if (this.serviceInvokerManager == null) {
this.serviceInvokerManager = new ServiceInvokerManager();
SpringBeanServiceInvoker springBeanServiceInvoker = new SpringBeanServiceInvoker();
springBeanServiceInvoker.setApplicationContext(getApplicationContext());
springBeanServiceInvoker.setThreadPoolExecutor(threadPoolExecutor);
springBeanServiceInvoker.setSagaJsonParser(getSagaJsonParser());
this.serviceInvokerManager.putServiceInvoker(DomainConstants.SERVICE_TYPE_SPRING_BEAN,
springBeanServiceInvoker);
}
if (this.scriptEngineManager == null) {
this.scriptEngineManager = new ScriptEngineManager();
}
}
protected ProcessControllerImpl createProcessorController(ProcessCtrlEventPublisher eventPublisher) throws Exception {
StateMachineProcessRouter stateMachineProcessRouter = new StateMachineProcessRouter();
stateMachineProcessRouter.initDefaultStateRouters();
loadStateRouterInterceptors(stateMachineProcessRouter.getStateRouters());
StateMachineProcessHandler stateMachineProcessHandler = new StateMachineProcessHandler();
stateMachineProcessHandler.initDefaultHandlers();
loadStateHandlerInterceptors(stateMachineProcessHandler.getStateHandlers());
DefaultRouterHandler defaultRouterHandler = new DefaultRouterHandler();
defaultRouterHandler.setEventPublisher(eventPublisher);
Map<String, ProcessRouter> processRouterMap = new HashMap<>(1);
processRouterMap.put(ProcessType.STATE_LANG.getCode(), stateMachineProcessRouter);
defaultRouterHandler.setProcessRouters(processRouterMap);
CustomizeBusinessProcessor customizeBusinessProcessor = new CustomizeBusinessProcessor();
Map<String, ProcessHandler> processHandlerMap = new HashMap<>(1);
processHandlerMap.put(ProcessType.STATE_LANG.getCode(), stateMachineProcessHandler);
customizeBusinessProcessor.setProcessHandlers(processHandlerMap);
Map<String, RouterHandler> routerHandlerMap = new HashMap<>(1);
routerHandlerMap.put(ProcessType.STATE_LANG.getCode(), defaultRouterHandler);
customizeBusinessProcessor.setRouterHandlers(routerHandlerMap);
ProcessControllerImpl processorController = new ProcessControllerImpl();
processorController.setBusinessProcessor(customizeBusinessProcessor);
return processorController;
}
protected void loadStateHandlerInterceptors(Map<String, StateHandler> stateHandlerMap) {
for (StateHandler stateHandler : stateHandlerMap.values()) {
if (stateHandler instanceof InterceptableStateHandler) {
InterceptableStateHandler interceptableStateHandler = (InterceptableStateHandler) stateHandler;
List<StateHandlerInterceptor> interceptorList = EnhancedServiceLoader.loadAll(StateHandlerInterceptor.class);
for (StateHandlerInterceptor interceptor : interceptorList) {
if (interceptor.match(interceptableStateHandler.getClass())) {
interceptableStateHandler.addInterceptor(interceptor);
}
if (interceptor instanceof ApplicationContextAware) {
((ApplicationContextAware) interceptor).setApplicationContext(getApplicationContext());
}
}
}
}
}
protected void loadStateRouterInterceptors(Map<String, StateRouter> stateRouterMap) {
for (StateRouter stateRouter : stateRouterMap.values()) {
if (stateRouter instanceof InterceptableStateRouter) {
InterceptableStateRouter interceptableStateRouter = (InterceptableStateRouter) stateRouter;
List<StateRouterInterceptor> interceptorList = EnhancedServiceLoader.loadAll(StateRouterInterceptor.class);
for (StateRouterInterceptor interceptor : interceptorList) {
if (interceptor.match(interceptableStateRouter.getClass())) {
interceptableStateRouter.addInterceptor(interceptor);
}
if (interceptor instanceof ApplicationContextAware) {
((ApplicationContextAware) interceptor).setApplicationContext(getApplicationContext());
}
}
}
}
}
@Override
public void afterPropertiesSet() throws Exception {
init();
}
@Override
public StateLogStore getStateLogStore() {
return this.stateLogStore;
}
public void setStateLogStore(StateLogStore stateLogStore) {
this.stateLogStore = stateLogStore;
}
@Override
public StateLangStore getStateLangStore() {
return stateLangStore;
}
public void setStateLangStore(StateLangStore stateLangStore) {
this.stateLangStore = stateLangStore;
}
@Override
public ExpressionFactoryManager getExpressionFactoryManager() {
return this.expressionFactoryManager;
}
public void setExpressionFactoryManager(ExpressionFactoryManager expressionFactoryManager) {
this.expressionFactoryManager = expressionFactoryManager;
}
@Override
public EvaluatorFactoryManager getEvaluatorFactoryManager() {
return this.evaluatorFactoryManager;
}
public void setEvaluatorFactoryManager(EvaluatorFactoryManager evaluatorFactoryManager) {
this.evaluatorFactoryManager = evaluatorFactoryManager;
}
@Override
public String getCharset() {
return this.charset;
}
public void setCharset(String charset) {
this.charset = charset;
}
@Override
public StateMachineRepository getStateMachineRepository() {
return stateMachineRepository;
}
public void setStateMachineRepository(StateMachineRepository stateMachineRepository) {
this.stateMachineRepository = stateMachineRepository;
}
@Override
public StatusDecisionStrategy getStatusDecisionStrategy() {
return statusDecisionStrategy;
}
public void setStatusDecisionStrategy(StatusDecisionStrategy statusDecisionStrategy) {
this.statusDecisionStrategy = statusDecisionStrategy;
}
@Override
public SeqGenerator getSeqGenerator() {
if (seqGenerator == null) {
synchronized (this) {
if (seqGenerator == null) {
seqGenerator = new SpringJvmUUIDSeqGenerator();
}
}
}
return seqGenerator;
}
public void setSeqGenerator(SeqGenerator seqGenerator) {
this.seqGenerator = seqGenerator;
}
@Override
public ProcessCtrlEventPublisher getProcessCtrlEventPublisher() {
return syncProcessCtrlEventPublisher;
}
@Override
public ProcessCtrlEventPublisher getAsyncProcessCtrlEventPublisher() {
return asyncProcessCtrlEventPublisher;
}
public void setAsyncProcessCtrlEventPublisher(ProcessCtrlEventPublisher asyncProcessCtrlEventPublisher) {
this.asyncProcessCtrlEventPublisher = asyncProcessCtrlEventPublisher;
}
@Override
public ApplicationContext getApplicationContext() {
return applicationContext;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
@Override
public ThreadPoolExecutor getThreadPoolExecutor() {
return threadPoolExecutor;
}
public void setThreadPoolExecutor(ThreadPoolExecutor threadPoolExecutor) {
this.threadPoolExecutor = threadPoolExecutor;
}
@Override
public boolean isEnableAsync() {
return enableAsync;
}
public void setEnableAsync(boolean enableAsync) {
this.enableAsync = enableAsync;
}
@Override
public StateLogRepository getStateLogRepository() {
return stateLogRepository;
}
public void setStateLogRepository(StateLogRepository stateLogRepository) {
this.stateLogRepository = stateLogRepository;
}
public void setSyncProcessCtrlEventPublisher(ProcessCtrlEventPublisher syncProcessCtrlEventPublisher) {
this.syncProcessCtrlEventPublisher = syncProcessCtrlEventPublisher;
}
public void setResources(Resource[] resources) {
this.resources = resources;
}
@Override
public ServiceInvokerManager getServiceInvokerManager() {
return serviceInvokerManager;
}
public void setServiceInvokerManager(ServiceInvokerManager serviceInvokerManager) {
this.serviceInvokerManager = serviceInvokerManager;
}
@Override
public String getDefaultTenantId() {
return defaultTenantId;
}
public void setDefaultTenantId(String defaultTenantId) {
this.defaultTenantId = defaultTenantId;
}
@Override
public int getTransOperationTimeout() {
return transOperationTimeout;
}
public void setTransOperationTimeout(int transOperationTimeout) {
this.transOperationTimeout = transOperationTimeout;
}
@Override
public int getServiceInvokeTimeout() {
return serviceInvokeTimeout;
}
public void setServiceInvokeTimeout(int serviceInvokeTimeout) {
this.serviceInvokeTimeout = serviceInvokeTimeout;
}
@Override
public ScriptEngineManager getScriptEngineManager() {
return scriptEngineManager;
}
public void setScriptEngineManager(ScriptEngineManager scriptEngineManager) {
this.scriptEngineManager = scriptEngineManager;
}
public String getSagaJsonParser() {
return sagaJsonParser;
}
public void setSagaJsonParser(String sagaJsonParser) {
this.sagaJsonParser = sagaJsonParser;
}
public boolean isSagaRetryPersistModeUpdate() {
return sagaRetryPersistModeUpdate;
}
public void setSagaRetryPersistModeUpdate(boolean sagaRetryPersistModeUpdate) {
this.sagaRetryPersistModeUpdate = sagaRetryPersistModeUpdate;
}
public boolean isSagaCompensatePersistModeUpdate() {
return sagaCompensatePersistModeUpdate;
}
public void setSagaCompensatePersistModeUpdate(boolean sagaCompensatePersistModeUpdate) {
this.sagaCompensatePersistModeUpdate = sagaCompensatePersistModeUpdate;
}
}

View File

@@ -0,0 +1,707 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.impl;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import io.seata.common.exception.FrameworkErrorCode;
import io.seata.common.util.CollectionUtils;
import io.seata.saga.engine.AsyncCallback;
import io.seata.saga.engine.StateMachineConfig;
import io.seata.saga.engine.StateMachineEngine;
import io.seata.saga.engine.exception.EngineExecutionException;
import io.seata.saga.engine.exception.ForwardInvalidException;
import io.seata.saga.engine.pcext.StateInstruction;
import io.seata.saga.engine.pcext.utils.EngineUtils;
import io.seata.saga.engine.pcext.utils.LoopTaskUtils;
import io.seata.saga.engine.pcext.utils.ParameterUtils;
import io.seata.saga.engine.utils.ProcessContextBuilder;
import io.seata.saga.proctrl.ProcessContext;
import io.seata.saga.proctrl.ProcessType;
import io.seata.saga.statelang.domain.DomainConstants;
import io.seata.saga.statelang.domain.ExecutionStatus;
import io.seata.saga.statelang.domain.State;
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.TaskState.Loop;
import io.seata.saga.statelang.domain.impl.AbstractTaskState;
import io.seata.saga.statelang.domain.impl.CompensationTriggerStateImpl;
import io.seata.saga.statelang.domain.impl.LoopStartStateImpl;
import io.seata.saga.statelang.domain.impl.ServiceTaskStateImpl;
import io.seata.saga.statelang.domain.impl.StateMachineInstanceImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
/**
* ProcessCtrl-based state machine engine
*
* @author lorne.cl
*/
public class ProcessCtrlStateMachineEngine implements StateMachineEngine {
private static final Logger LOGGER = LoggerFactory.getLogger(ProcessCtrlStateMachineEngine.class);
private StateMachineConfig stateMachineConfig;
private static void nullSafeCopy(Map<String, Object> srcMap, Map<String, Object> destMap) {
srcMap.forEach((key, value) -> {
if (value != null) {
destMap.put(key, value);
}
});
}
@Override
public StateMachineInstance start(String stateMachineName, String tenantId, Map<String, Object> startParams)
throws EngineExecutionException {
return startInternal(stateMachineName, tenantId, null, startParams, false, null);
}
@Override
public StateMachineInstance startAsync(String stateMachineName, String tenantId, Map<String, Object> startParams,
AsyncCallback callback) throws EngineExecutionException {
return startInternal(stateMachineName, tenantId, null, startParams, true, callback);
}
@Override
public StateMachineInstance startWithBusinessKey(String stateMachineName, String tenantId, String businessKey,
Map<String, Object> startParams) throws EngineExecutionException {
return startInternal(stateMachineName, tenantId, businessKey, startParams, false, null);
}
@Override
public StateMachineInstance startWithBusinessKeyAsync(String stateMachineName, String tenantId, String businessKey,
Map<String, Object> startParams, AsyncCallback callback)
throws EngineExecutionException {
return startInternal(stateMachineName, tenantId, businessKey, startParams, true, callback);
}
private StateMachineInstance startInternal(String stateMachineName, String tenantId, String businessKey,
Map<String, Object> startParams, boolean async, AsyncCallback callback)
throws EngineExecutionException {
if (async && !stateMachineConfig.isEnableAsync()) {
throw new EngineExecutionException(
"Asynchronous start is disabled. please set StateMachineConfig.enableAsync=true first.",
FrameworkErrorCode.AsynchronousStartDisabled);
}
if (StringUtils.isEmpty(tenantId)) {
tenantId = stateMachineConfig.getDefaultTenantId();
}
StateMachineInstance instance = createMachineInstance(stateMachineName, tenantId, businessKey, startParams);
ProcessContextBuilder contextBuilder = ProcessContextBuilder.create().withProcessType(ProcessType.STATE_LANG)
.withOperationName(DomainConstants.OPERATION_NAME_START).withAsyncCallback(callback).withInstruction(
new StateInstruction(stateMachineName, tenantId)).withStateMachineInstance(instance)
.withStateMachineConfig(getStateMachineConfig()).withStateMachineEngine(this);
Map<String, Object> contextVariables;
if (startParams != null) {
contextVariables = new ConcurrentHashMap<>(startParams.size());
nullSafeCopy(startParams, contextVariables);
} else {
contextVariables = new ConcurrentHashMap<>();
}
instance.setContext(contextVariables);
contextBuilder.withStateMachineContextVariables(contextVariables);
contextBuilder.withIsAsyncExecution(async);
ProcessContext processContext = contextBuilder.build();
if (instance.getStateMachine().isPersist() && stateMachineConfig.getStateLogStore() != null) {
stateMachineConfig.getStateLogStore().recordStateMachineStarted(instance, processContext);
}
if (StringUtils.isEmpty(instance.getId())) {
instance.setId(
stateMachineConfig.getSeqGenerator().generate(DomainConstants.SEQ_ENTITY_STATE_MACHINE_INST));
}
StateInstruction stateInstruction = processContext.getInstruction(StateInstruction.class);
Loop loop = LoopTaskUtils.getLoopConfig(processContext, stateInstruction.getState(processContext));
if (null != loop) {
stateInstruction.setTemporaryState(new LoopStartStateImpl());
}
if (async) {
stateMachineConfig.getAsyncProcessCtrlEventPublisher().publish(processContext);
} else {
stateMachineConfig.getProcessCtrlEventPublisher().publish(processContext);
}
return instance;
}
private StateMachineInstance createMachineInstance(String stateMachineName, String tenantId, String businessKey,
Map<String, Object> startParams) {
StateMachine stateMachine = stateMachineConfig.getStateMachineRepository().getStateMachine(stateMachineName,
tenantId);
if (stateMachine == null) {
throw new EngineExecutionException("StateMachine[" + stateMachineName + "] is not exists",
FrameworkErrorCode.ObjectNotExists);
}
StateMachineInstanceImpl inst = new StateMachineInstanceImpl();
inst.setStateMachine(stateMachine);
inst.setMachineId(stateMachine.getId());
inst.setTenantId(tenantId);
inst.setBusinessKey(businessKey);
inst.setStartParams(startParams);
if (startParams != null) {
if (StringUtils.hasText(businessKey)) {
startParams.put(DomainConstants.VAR_NAME_BUSINESSKEY, businessKey);
}
String parentId = (String)startParams.get(DomainConstants.VAR_NAME_PARENT_ID);
if (StringUtils.hasText(parentId)) {
inst.setParentId(parentId);
startParams.remove(DomainConstants.VAR_NAME_PARENT_ID);
}
}
inst.setStatus(ExecutionStatus.RU);
inst.setRunning(true);
inst.setGmtStarted(new Date());
inst.setGmtUpdated(inst.getGmtStarted());
return inst;
}
@Override
public StateMachineInstance forward(String stateMachineInstId, Map<String, Object> replaceParams)
throws EngineExecutionException {
return forwardInternal(stateMachineInstId, replaceParams, false, false, null);
}
@Override
public StateMachineInstance forwardAsync(String stateMachineInstId, Map<String, Object> replaceParams,
AsyncCallback callback) throws EngineExecutionException {
return forwardInternal(stateMachineInstId, replaceParams, false, true, callback);
}
protected StateMachineInstance forwardInternal(String stateMachineInstId, Map<String, Object> replaceParams,
boolean skip, boolean async, AsyncCallback callback)
throws EngineExecutionException {
StateMachineInstance stateMachineInstance = reloadStateMachineInstance(stateMachineInstId);
if (stateMachineInstance == null) {
throw new ForwardInvalidException("StateMachineInstance is not exits",
FrameworkErrorCode.StateMachineInstanceNotExists);
}
if (ExecutionStatus.SU.equals(stateMachineInstance.getStatus())
&& stateMachineInstance.getCompensationStatus() == null) {
return stateMachineInstance;
}
ExecutionStatus[] acceptStatus = new ExecutionStatus[] {ExecutionStatus.FA, ExecutionStatus.UN, ExecutionStatus.RU};
checkStatus(stateMachineInstance, acceptStatus, null, stateMachineInstance.getStatus(), null, "forward");
List<StateInstance> actList = stateMachineInstance.getStateList();
if (CollectionUtils.isEmpty(actList)) {
throw new ForwardInvalidException("StateMachineInstance[id:" + stateMachineInstId
+ "] has no stateInstance, pls start a new StateMachine execution instead",
FrameworkErrorCode.OperationDenied);
}
StateInstance lastForwardState = findOutLastForwardStateInstance(actList);
if (lastForwardState == null) {
throw new ForwardInvalidException(
"StateMachineInstance[id:" + stateMachineInstId + "] Cannot find last forward execution stateInstance",
FrameworkErrorCode.OperationDenied);
}
ProcessContextBuilder contextBuilder = ProcessContextBuilder.create().withProcessType(ProcessType.STATE_LANG)
.withOperationName(DomainConstants.OPERATION_NAME_FORWARD).withAsyncCallback(callback)
.withStateMachineInstance(stateMachineInstance).withStateInstance(lastForwardState).withStateMachineConfig(
getStateMachineConfig()).withStateMachineEngine(this);
contextBuilder.withIsAsyncExecution(async);
ProcessContext context = contextBuilder.build();
Map<String, Object> contextVariables = getStateMachineContextVariables(stateMachineInstance);
if (replaceParams != null) {
contextVariables.putAll(replaceParams);
}
putBusinesskeyToContextariables(stateMachineInstance, contextVariables);
ConcurrentHashMap<String, Object> concurrentContextVariables = new ConcurrentHashMap<>(contextVariables.size());
nullSafeCopy(contextVariables, concurrentContextVariables);
context.setVariable(DomainConstants.VAR_NAME_STATEMACHINE_CONTEXT, concurrentContextVariables);
stateMachineInstance.setContext(concurrentContextVariables);
String originStateName = EngineUtils.getOriginStateName(lastForwardState);
State lastState = stateMachineInstance.getStateMachine().getState(originStateName);
Loop loop = LoopTaskUtils.getLoopConfig(context, lastState);
if (null != loop && ExecutionStatus.SU.equals(lastForwardState.getStatus())) {
lastForwardState = LoopTaskUtils.findOutLastNeedForwardStateInstance(context);
}
context.setVariable(lastForwardState.getName() + DomainConstants.VAR_NAME_RETRIED_STATE_INST_ID,
lastForwardState.getId());
if (DomainConstants.STATE_TYPE_SUB_STATE_MACHINE.equals(lastForwardState.getType()) && !ExecutionStatus.SU
.equals(lastForwardState.getCompensationStatus())) {
context.setVariable(DomainConstants.VAR_NAME_IS_FOR_SUB_STATMACHINE_FORWARD, true);
}
if (!ExecutionStatus.SU.equals(lastForwardState.getStatus())) {
lastForwardState.setIgnoreStatus(true);
}
try {
StateInstruction inst = new StateInstruction();
inst.setTenantId(stateMachineInstance.getTenantId());
inst.setStateMachineName(stateMachineInstance.getStateMachine().getName());
if (skip || ExecutionStatus.SU.equals(lastForwardState.getStatus())) {
String next = null;
State state = stateMachineInstance.getStateMachine().getState(EngineUtils.getOriginStateName(lastForwardState));
if (state != null && state instanceof AbstractTaskState) {
next = ((AbstractTaskState)state).getNext();
}
if (StringUtils.isEmpty(next)) {
LOGGER.warn(
"Last Forward execution StateInstance was succeed, and it has not Next State , skip forward "
+ "operation");
return stateMachineInstance;
}
inst.setStateName(next);
} else {
if (ExecutionStatus.RU.equals(lastForwardState.getStatus())
&& !EngineUtils.isTimeout(lastForwardState.getGmtStarted(), stateMachineConfig.getServiceInvokeTimeout())) {
throw new EngineExecutionException(
"State [" + lastForwardState.getName() + "] is running, operation[forward] denied", FrameworkErrorCode.OperationDenied);
}
inst.setStateName(EngineUtils.getOriginStateName(lastForwardState));
}
context.setInstruction(inst);
stateMachineInstance.setStatus(ExecutionStatus.RU);
stateMachineInstance.setRunning(true);
if (LOGGER.isInfoEnabled()) {
LOGGER.info("Operation [forward] started stateMachineInstance[id:" + stateMachineInstance.getId() + "]");
}
if (stateMachineInstance.getStateMachine().isPersist()) {
stateMachineConfig.getStateLogStore().recordStateMachineRestarted(stateMachineInstance, context);
}
loop = LoopTaskUtils.getLoopConfig(context, inst.getState(context));
if (null != loop) {
inst.setTemporaryState(new LoopStartStateImpl());
}
if (async) {
stateMachineConfig.getAsyncProcessCtrlEventPublisher().publish(context);
} else {
stateMachineConfig.getProcessCtrlEventPublisher().publish(context);
}
} catch (EngineExecutionException e) {
LOGGER.error("Operation [forward] failed", e);
throw e;
}
return stateMachineInstance;
}
private Map<String, Object> getStateMachineContextVariables(StateMachineInstance stateMachineInstance) {
Map<String, Object> contextVariables = stateMachineInstance.getEndParams();
if (CollectionUtils.isEmpty(contextVariables)) {
contextVariables = replayContextVariables(stateMachineInstance);
}
return contextVariables;
}
protected Map<String, Object> replayContextVariables(StateMachineInstance stateMachineInstance) {
Map<String, Object> contextVariables = new HashMap<>();
if (stateMachineInstance.getStartParams() == null) {
contextVariables.putAll(stateMachineInstance.getStartParams());
}
List<StateInstance> stateInstanceList = stateMachineInstance.getStateList();
if (CollectionUtils.isEmpty(stateInstanceList)) {
return contextVariables;
}
for (StateInstance stateInstance : stateInstanceList) {
Object serviceOutputParams = stateInstance.getOutputParams();
if (serviceOutputParams != null) {
ServiceTaskStateImpl state = (ServiceTaskStateImpl)stateMachineInstance.getStateMachine().getState(
EngineUtils.getOriginStateName(stateInstance));
if (state == null) {
throw new EngineExecutionException(
"Cannot find State by state name [" + stateInstance.getName() + "], may be this is a bug",
FrameworkErrorCode.ObjectNotExists);
}
if (CollectionUtils.isNotEmpty(state.getOutput())) {
try {
Map<String, Object> outputVariablesToContext = ParameterUtils
.createOutputParams(stateMachineConfig.getExpressionFactoryManager(), state,
serviceOutputParams);
if (CollectionUtils.isNotEmpty(outputVariablesToContext)) {
contextVariables.putAll(outputVariablesToContext);
}
if (StringUtils.hasLength(stateInstance.getBusinessKey())) {
contextVariables.put(
state.getName() + DomainConstants.VAR_NAME_BUSINESSKEY,
stateInstance.getBusinessKey());
}
} catch (Exception e) {
throw new EngineExecutionException(e, "Context variables replay faied",
FrameworkErrorCode.ContextVariableReplayFailed);
}
}
}
}
return contextVariables;
}
/**
* Find the last instance of the forward execution state
*
* @param stateInstanceList
* @return
*/
public StateInstance findOutLastForwardStateInstance(List<StateInstance> stateInstanceList) {
StateInstance lastForwardStateInstance = null;
for (int i = stateInstanceList.size() - 1; i >= 0; i--) {
StateInstance stateInstance = stateInstanceList.get(i);
if (!stateInstance.isForCompensation()) {
if (ExecutionStatus.SU.equals(stateInstance.getCompensationStatus())) {
continue;
}
if (DomainConstants.STATE_TYPE_SUB_STATE_MACHINE.equals(stateInstance.getType())) {
StateInstance finalState = stateInstance;
while (StringUtils.hasText(finalState.getStateIdRetriedFor())) {
finalState = stateMachineConfig.getStateLogStore().getStateInstance(
finalState.getStateIdRetriedFor(), finalState.getMachineInstanceId());
}
List<StateMachineInstance> subInst = stateMachineConfig.getStateLogStore()
.queryStateMachineInstanceByParentId(EngineUtils.generateParentId(finalState));
if (CollectionUtils.isNotEmpty(subInst)) {
if (ExecutionStatus.SU.equals(subInst.get(0).getCompensationStatus())) {
continue;
}
if (ExecutionStatus.UN.equals(subInst.get(0).getCompensationStatus())) {
throw new ForwardInvalidException(
"Last forward execution state instance is SubStateMachine and compensation status is "
+ "[UN], Operation[forward] denied, stateInstanceId:"
+ stateInstance.getId(), FrameworkErrorCode.OperationDenied);
}
}
} else if (ExecutionStatus.UN.equals(stateInstance.getCompensationStatus())) {
throw new ForwardInvalidException(
"Last forward execution state instance compensation status is [UN], Operation[forward] "
+ "denied, stateInstanceId:"
+ stateInstance.getId(), FrameworkErrorCode.OperationDenied);
}
lastForwardStateInstance = stateInstance;
break;
}
}
return lastForwardStateInstance;
}
@Override
public StateMachineInstance compensate(String stateMachineInstId, Map<String, Object> replaceParams)
throws EngineExecutionException {
return compensateInternal(stateMachineInstId, replaceParams, false, null);
}
@Override
public StateMachineInstance compensateAsync(String stateMachineInstId, Map<String, Object> replaceParams,
AsyncCallback callback) throws EngineExecutionException {
return compensateInternal(stateMachineInstId, replaceParams, true, callback);
}
public StateMachineInstance compensateInternal(String stateMachineInstId, Map<String, Object> replaceParams,
boolean async, AsyncCallback callback)
throws EngineExecutionException {
StateMachineInstance stateMachineInstance = reloadStateMachineInstance(stateMachineInstId);
if (stateMachineInstance == null) {
throw new EngineExecutionException("StateMachineInstance is not exits",
FrameworkErrorCode.StateMachineInstanceNotExists);
}
if (ExecutionStatus.SU.equals(stateMachineInstance.getCompensationStatus())) {
return stateMachineInstance;
}
if (stateMachineInstance.getCompensationStatus() != null) {
ExecutionStatus[] denyStatus = new ExecutionStatus[] {ExecutionStatus.SU};
checkStatus(stateMachineInstance, null, denyStatus, null, stateMachineInstance.getCompensationStatus(),
"compensate");
}
if (replaceParams != null) {
stateMachineInstance.getEndParams().putAll(replaceParams);
}
ProcessContextBuilder contextBuilder = ProcessContextBuilder.create().withProcessType(ProcessType.STATE_LANG)
.withOperationName(DomainConstants.OPERATION_NAME_COMPENSATE).withAsyncCallback(callback)
.withStateMachineInstance(stateMachineInstance).withStateMachineConfig(getStateMachineConfig())
.withStateMachineEngine(this);
contextBuilder.withIsAsyncExecution(async);
ProcessContext context = contextBuilder.build();
Map<String, Object> contextVariables = getStateMachineContextVariables(stateMachineInstance);
if (replaceParams != null) {
contextVariables.putAll(replaceParams);
}
putBusinesskeyToContextariables(stateMachineInstance, contextVariables);
ConcurrentHashMap<String, Object> concurrentContextVariables = new ConcurrentHashMap<>(contextVariables.size());
nullSafeCopy(contextVariables, concurrentContextVariables);
context.setVariable(DomainConstants.VAR_NAME_STATEMACHINE_CONTEXT, concurrentContextVariables);
stateMachineInstance.setContext(concurrentContextVariables);
CompensationTriggerStateImpl tempCompensationTriggerState = new CompensationTriggerStateImpl();
tempCompensationTriggerState.setStateMachine(stateMachineInstance.getStateMachine());
stateMachineInstance.setRunning(true);
if (LOGGER.isInfoEnabled()) {
LOGGER.info("Operation [compensate] start. stateMachineInstance[id:" + stateMachineInstance.getId() + "]");
}
if (stateMachineInstance.getStateMachine().isPersist()) {
stateMachineConfig.getStateLogStore().recordStateMachineRestarted(stateMachineInstance, context);
}
try {
StateInstruction inst = new StateInstruction();
inst.setTenantId(stateMachineInstance.getTenantId());
inst.setStateMachineName(stateMachineInstance.getStateMachine().getName());
inst.setTemporaryState(tempCompensationTriggerState);
context.setInstruction(inst);
if (async) {
stateMachineConfig.getAsyncProcessCtrlEventPublisher().publish(context);
} else {
stateMachineConfig.getProcessCtrlEventPublisher().publish(context);
}
} catch (EngineExecutionException e) {
LOGGER.error("Operation [compensate] failed", e);
throw e;
}
return stateMachineInstance;
}
@Override
public StateMachineInstance skipAndForward(String stateMachineInstId, Map<String, Object> replaceParams) throws EngineExecutionException {
return forwardInternal(stateMachineInstId, replaceParams, false, true, null);
}
@Override
public StateMachineInstance skipAndForwardAsync(String stateMachineInstId, AsyncCallback callback)
throws EngineExecutionException {
return forwardInternal(stateMachineInstId, null, false, true, callback);
}
/**
* override state machine instance
*
* @param instId
* @return
*/
@Override
public StateMachineInstance reloadStateMachineInstance(String instId) {
StateMachineInstance inst = stateMachineConfig.getStateLogStore().getStateMachineInstance(instId);
if (inst != null) {
StateMachine stateMachine = inst.getStateMachine();
if (stateMachine == null) {
stateMachine = stateMachineConfig.getStateMachineRepository().getStateMachineById(inst.getMachineId());
inst.setStateMachine(stateMachine);
}
if (stateMachine == null) {
throw new EngineExecutionException("StateMachine[id:" + inst.getMachineId() + "] not exist.",
FrameworkErrorCode.ObjectNotExists);
}
List<StateInstance> stateList = inst.getStateList();
if (CollectionUtils.isEmpty(stateList)) {
stateList = stateMachineConfig.getStateLogStore().queryStateInstanceListByMachineInstanceId(instId);
if (CollectionUtils.isNotEmpty(stateList)) {
for (StateInstance tmpStateInstance : stateList) {
inst.putStateInstance(tmpStateInstance.getId(), tmpStateInstance);
}
}
}
if (CollectionUtils.isEmpty(inst.getEndParams())) {
inst.setEndParams(replayContextVariables(inst));
}
}
return inst;
}
/**
* Check if the status is legal
*
* @param stateMachineInstance
* @param acceptStatus
* @param denyStatus
* @param status
* @param compenStatus
* @param operation
* @return
*/
protected boolean checkStatus(StateMachineInstance stateMachineInstance, ExecutionStatus[] acceptStatus,
ExecutionStatus[] denyStatus, ExecutionStatus status, ExecutionStatus compenStatus,
String operation) {
if (status != null && compenStatus != null) {
throw new EngineExecutionException("status and compensationStatus are not supported at the same time",
FrameworkErrorCode.InvalidParameter);
}
if (status == null && compenStatus == null) {
throw new EngineExecutionException("status and compensationStatus must input at least one",
FrameworkErrorCode.InvalidParameter);
}
if (ExecutionStatus.SU.equals(compenStatus)) {
String message = buildExceptionMessage(stateMachineInstance, null, null, null, ExecutionStatus.SU,
operation);
throw new EngineExecutionException(message, FrameworkErrorCode.OperationDenied);
}
if (stateMachineInstance.isRunning() && !EngineUtils.isTimeout(stateMachineInstance.getGmtUpdated(), stateMachineConfig.getTransOperationTimeout())) {
throw new EngineExecutionException(
"StateMachineInstance [id:" + stateMachineInstance.getId() + "] is running, operation[" + operation
+ "] denied", FrameworkErrorCode.OperationDenied);
}
if ((denyStatus == null || denyStatus.length == 0) && (acceptStatus == null || acceptStatus.length == 0)) {
throw new EngineExecutionException("StateMachineInstance[id:" + stateMachineInstance.getId()
+ "], acceptable status and deny status must input at least one", FrameworkErrorCode.InvalidParameter);
}
ExecutionStatus currentStatus = (status != null) ? status : compenStatus;
if (!(denyStatus == null || denyStatus.length == 0)) {
for (ExecutionStatus tempDenyStatus : denyStatus) {
if (tempDenyStatus.compareTo(currentStatus) == 0) {
String message = buildExceptionMessage(stateMachineInstance, acceptStatus, denyStatus, status,
compenStatus, operation);
throw new EngineExecutionException(message, FrameworkErrorCode.OperationDenied);
}
}
}
if (acceptStatus == null || acceptStatus.length == 0) {
return true;
} else {
for (ExecutionStatus tempStatus : acceptStatus) {
if (tempStatus.compareTo(currentStatus) == 0) {
return true;
}
}
}
String message = buildExceptionMessage(stateMachineInstance, acceptStatus, denyStatus, status, compenStatus,
operation);
throw new EngineExecutionException(message, FrameworkErrorCode.OperationDenied);
}
private String buildExceptionMessage(StateMachineInstance stateMachineInstance, ExecutionStatus[] acceptStatus,
ExecutionStatus[] denyStatus, ExecutionStatus status,
ExecutionStatus compenStatus, String operation) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("StateMachineInstance[id:").append(stateMachineInstance.getId()).append("]");
if (acceptStatus != null) {
stringBuilder.append(",acceptable status :");
for (ExecutionStatus tempStatus : acceptStatus) {
stringBuilder.append(tempStatus.toString());
stringBuilder.append(" ");
}
}
if (denyStatus != null) {
stringBuilder.append(",deny status:");
for (ExecutionStatus tempStatus : denyStatus) {
stringBuilder.append(tempStatus.toString());
stringBuilder.append(" ");
}
}
if (status != null) {
stringBuilder.append(",current status:");
stringBuilder.append(status.toString());
}
if (compenStatus != null) {
stringBuilder.append(",current compensation status:");
stringBuilder.append(compenStatus.toString());
}
stringBuilder.append(",so operation [").append(operation).append("] denied");
return stringBuilder.toString();
}
private void putBusinesskeyToContextariables(StateMachineInstance stateMachineInstance,
Map<String, Object> contextVariables) {
if (StringUtils.hasText(stateMachineInstance.getBusinessKey()) && !contextVariables.containsKey(
DomainConstants.VAR_NAME_BUSINESSKEY)) {
contextVariables.put(DomainConstants.VAR_NAME_BUSINESSKEY, stateMachineInstance.getBusinessKey());
}
}
@Override
public StateMachineConfig getStateMachineConfig() {
return stateMachineConfig;
}
public void setStateMachineConfig(StateMachineConfig stateMachineConfig) {
this.stateMachineConfig = stateMachineConfig;
}
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.invoker;
import io.seata.saga.statelang.domain.ServiceTaskState;
/**
* Service invoker
*
* @author lorne.cl
*/
public interface ServiceInvoker {
/**
* invoke service
* @param serviceTaskState
* @param input
* @return
* @throws Throwable
*/
Object invoke(ServiceTaskState serviceTaskState, Object... input) throws Throwable;
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.invoker;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import io.seata.saga.statelang.domain.DomainConstants;
import org.springframework.util.StringUtils;
/**
* Service Invoker Manager
*
* @author lorne.cl
*/
public class ServiceInvokerManager {
private Map<String, ServiceInvoker> serviceInvokerMap = new ConcurrentHashMap<>();
public ServiceInvoker getServiceInvoker(String serviceType) {
if (StringUtils.isEmpty(serviceType)) {
serviceType = DomainConstants.SERVICE_TYPE_SPRING_BEAN;
}
return serviceInvokerMap.get(serviceType);
}
public void putServiceInvoker(String serviceType, ServiceInvoker serviceInvoker) {
serviceInvokerMap.put(serviceType, serviceInvoker);
}
public Map<String, ServiceInvoker> getServiceInvokerMap() {
return serviceInvokerMap;
}
}

View File

@@ -0,0 +1,359 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.invoker.impl;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.atomic.AtomicInteger;
import io.seata.common.exception.FrameworkErrorCode;
import io.seata.common.util.CollectionUtils;
import io.seata.saga.engine.exception.EngineExecutionException;
import io.seata.saga.engine.invoker.ServiceInvoker;
import io.seata.saga.engine.pcext.handlers.ServiceTaskStateHandler;
import io.seata.saga.engine.utils.ExceptionUtils;
import io.seata.saga.statelang.domain.ServiceTaskState;
import io.seata.saga.statelang.domain.TaskState.Retry;
import io.seata.saga.statelang.domain.impl.ServiceTaskStateImpl;
import io.seata.saga.statelang.parser.JsonParser;
import io.seata.saga.statelang.parser.JsonParserFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
/**
* SpringBean Service Invoker
*
* @author lorne.cl
*/
public class SpringBeanServiceInvoker implements ServiceInvoker, ApplicationContextAware {
private static final Logger LOGGER = LoggerFactory.getLogger(SpringBeanServiceInvoker.class);
private ApplicationContext applicationContext;
private ThreadPoolExecutor threadPoolExecutor;
private String sagaJsonParser;
@Override
public Object invoke(ServiceTaskState serviceTaskState, Object... input) throws Throwable {
ServiceTaskStateImpl state = (ServiceTaskStateImpl) serviceTaskState;
if (state.isAsync()) {
if (threadPoolExecutor == null) {
if (LOGGER.isWarnEnabled()) {
LOGGER.warn(
"threadPoolExecutor is null, Service[{}.{}] cannot execute asynchronously, executing "
+ "synchronously now. stateName: {}",
state.getServiceName(), state.getServiceMethod(), state.getName());
}
return doInvoke(state, input);
}
if (LOGGER.isInfoEnabled()) {
LOGGER.info("Submit Service[{}.{}] to asynchronously executing. stateName: {}", state.getServiceName(),
state.getServiceMethod(), state.getName());
}
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
try {
doInvoke(state, input);
} catch (Throwable e) {
LOGGER.error("Invoke Service[" + state.getServiceName() + "." + state.getServiceMethod() + "] failed.", e);
}
}
});
return null;
} else {
return doInvoke(state, input);
}
}
protected Object doInvoke(ServiceTaskStateImpl state, Object[] input) throws Throwable {
Object bean = applicationContext.getBean(state.getServiceName());
Method method = state.getMethod();
if (method == null) {
synchronized (state) {
method = state.getMethod();
if (method == null) {
method = findMethod(bean.getClass(), state.getServiceMethod(), state.getParameterTypes());
if (method != null) {
state.setMethod(method);
}
}
}
}
if (method == null) {
throw new EngineExecutionException(
"No such method[" + state.getServiceMethod() + "] on BeanClass[" + bean.getClass() + "]",
FrameworkErrorCode.NoSuchMethod);
}
Object[] args = new Object[method.getParameterCount()];
try {
Class[] paramTypes = method.getParameterTypes();
if (input != null && input.length > 0) {
int len = input.length < paramTypes.length ? input.length : paramTypes.length;
for (int i = 0; i < len; i++) {
args[i] = toJavaObject(input[i], paramTypes[i]);
}
}
} catch (Exception e) {
throw new EngineExecutionException(e,
"Input to java object error, Method[" + state.getServiceMethod() + "] on BeanClass[" + bean.getClass()
+ "]", FrameworkErrorCode.InvalidParameter);
}
if (!Modifier.isPublic(method.getModifiers())) {
throw new EngineExecutionException("Method[" + method.getName() + "] must be public",
FrameworkErrorCode.MethodNotPublic);
}
Map<Retry, AtomicInteger> retryCountMap = new HashMap<>();
while (true) {
try {
return invokeMethod(bean, method, args);
} catch (Throwable e) {
Retry matchedRetryConfig = matchRetryConfig(state.getRetry(), e);
if (matchedRetryConfig == null) {
throw e;
}
AtomicInteger retryCount = CollectionUtils.computeIfAbsent(retryCountMap, matchedRetryConfig,
key -> new AtomicInteger(0));
if (retryCount.intValue() >= matchedRetryConfig.getMaxAttempts()) {
throw e;
}
double intervalSeconds = matchedRetryConfig.getIntervalSeconds();
double backoffRate = matchedRetryConfig.getBackoffRate();
long currentInterval = (long) (retryCount.intValue() > 0 ?
(intervalSeconds * backoffRate * retryCount.intValue() * 1000) : (intervalSeconds * 1000));
if (LOGGER.isWarnEnabled()) {
LOGGER.warn("Invoke Service[" + state.getServiceName() + "." + state.getServiceMethod() + "] failed, will retry after "
+ currentInterval + " millis, current retry count: " + retryCount.intValue(), e);
}
try {
Thread.sleep(currentInterval);
} catch (InterruptedException e1) {
LOGGER.warn("Retry interval sleep error", e1);
}
retryCount.incrementAndGet();
}
}
}
private Retry matchRetryConfig(List<Retry> retryList, Throwable e) {
if (CollectionUtils.isNotEmpty(retryList)) {
for (Retry retryConfig : retryList) {
List<String> exceptions = retryConfig.getExceptions();
if (CollectionUtils.isEmpty(exceptions)) {
// Exceptions not configured, Match current exception if it is NetException.
if (ExceptionUtils.isNetException(e)) {
return retryConfig;
}
} else {
List<Class<? extends Exception>> exceptionClasses = retryConfig.getExceptionClasses();
if (exceptionClasses == null) {
synchronized (retryConfig) {
exceptionClasses = retryConfig.getExceptionClasses();
if (exceptionClasses == null) {
exceptionClasses = new ArrayList<>(exceptions.size());
for (String expStr : exceptions) {
Class<? extends Exception> expClass = null;
try {
expClass = (Class<? extends Exception>) ServiceTaskStateHandler.class
.getClassLoader().loadClass(expStr);
} catch (Exception e1) {
LOGGER.warn("Cannot Load Exception Class by getClass().getClassLoader()", e1);
try {
expClass = (Class<? extends Exception>) Thread.currentThread()
.getContextClassLoader().loadClass(expStr);
} catch (Exception e2) {
LOGGER.warn(
"Cannot Load Exception Class by Thread.currentThread()"
+ ".getContextClassLoader()",
e2);
}
}
if (expClass != null) {
exceptionClasses.add(expClass);
}
}
retryConfig.setExceptionClasses(exceptionClasses);
}
}
}
for (Class<? extends Exception> expClass : exceptionClasses) {
if (expClass.isAssignableFrom(e.getClass())) {
return retryConfig;
}
}
}
}
}
return null;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
public void setThreadPoolExecutor(ThreadPoolExecutor threadPoolExecutor) {
this.threadPoolExecutor = threadPoolExecutor;
}
protected Method findMethod(Class<?> clazz, String methodName, List<String> parameterTypes) {
if (CollectionUtils.isEmpty(parameterTypes)) {
return BeanUtils.findDeclaredMethodWithMinimalParameters(clazz, methodName);
} else {
Class[] paramClassTypes = new Class[parameterTypes.size()];
for (int i = 0; i < parameterTypes.size(); i++) {
paramClassTypes[i] = classForName(parameterTypes.get(i));
}
return BeanUtils.findDeclaredMethod(clazz, methodName, paramClassTypes);
}
}
protected Class classForName(String className) {
Class clazz = getPrimitiveClass(className);
if (clazz == null) {
try {
clazz = Class.forName(className);
} catch (ClassNotFoundException e) {
LOGGER.error(e.getMessage(), e);
}
}
if (clazz == null) {
try {
clazz = Class.forName(className, true, Thread.currentThread().getContextClassLoader());
} catch (ClassNotFoundException e) {
LOGGER.error(e.getMessage(), e);
}
}
if (clazz == null) {
throw new EngineExecutionException("Parameter class not found [" + className + "]",
FrameworkErrorCode.ObjectNotExists);
}
return clazz;
}
protected Object invokeMethod(Object serviceBean, Method method, Object... input) throws Throwable {
try {
return method.invoke(serviceBean, input);
} catch (InvocationTargetException e) {
Throwable targetExp = e.getTargetException();
if (targetExp == null) {
throw new EngineExecutionException(e, e.getMessage(), FrameworkErrorCode.MethodInvokeError);
}
throw targetExp;
}
}
protected Object toJavaObject(Object value, Class paramType) {
if (value == null) {
return value;
}
if (paramType.isAssignableFrom(value.getClass())) {
return value;
} else if (isPrimitive(paramType)) {
return value;
} else {
JsonParser jsonParser = JsonParserFactory.getJsonParser(getSagaJsonParser());
if (jsonParser == null) {
throw new RuntimeException("Cannot get JsonParser by name : " + getSagaJsonParser());
}
String jsonValue = jsonParser.toJsonString(value, true, false);
return jsonParser.parse(jsonValue, paramType, false);
}
}
protected boolean isPrimitive(Class<?> clazz) {
return clazz.isPrimitive() //
|| clazz == Boolean.class //
|| clazz == Character.class //
|| clazz == Byte.class //
|| clazz == Short.class //
|| clazz == Integer.class //
|| clazz == Long.class //
|| clazz == Float.class //
|| clazz == Double.class //
|| clazz == BigInteger.class //
|| clazz == BigDecimal.class //
|| clazz == String.class //
|| clazz == java.util.Date.class //
|| clazz == java.sql.Date.class //
|| clazz == java.sql.Time.class //
|| clazz == java.sql.Timestamp.class //
|| clazz.isEnum() //
;
}
protected Class getPrimitiveClass(String className) {
if (boolean.class.getName().equals(className)) {
return boolean.class;
} else if (char.class.getName().equals(className)) {
return char.class;
} else if (byte.class.getName().equals(className)) {
return byte.class;
} else if (short.class.getName().equals(className)) {
return short.class;
} else if (int.class.getName().equals(className)) {
return int.class;
} else if (long.class.getName().equals(className)) {
return long.class;
} else if (float.class.getName().equals(className)) {
return float.class;
} else if (double.class.getName().equals(className)) {
return double.class;
} else {
return null;
}
}
public String getSagaJsonParser() {
return sagaJsonParser;
}
public void setSagaJsonParser(String sagaJsonParser) {
this.sagaJsonParser = sagaJsonParser;
}
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.pcext;
import java.util.List;
/**
* Interceptible State Handler
*
* @author lorne.cl
*/
public interface InterceptableStateHandler extends StateHandler {
List<StateHandlerInterceptor> getInterceptors();
void addInterceptor(StateHandlerInterceptor interceptor);
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.pcext;
import java.util.List;
/**
* Interceptable State Router
*
* @author lorne.cl
*/
public interface InterceptableStateRouter extends StateRouter {
List<StateRouterInterceptor> getInterceptors();
void addInterceptor(StateRouterInterceptor interceptor);
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.pcext;
import io.seata.saga.engine.exception.EngineExecutionException;
import io.seata.saga.proctrl.ProcessContext;
/**
* State Handler
*
* @author lorne.cl
*/
public interface StateHandler {
void process(ProcessContext context) throws EngineExecutionException;
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.pcext;
import io.seata.saga.engine.exception.EngineExecutionException;
import io.seata.saga.proctrl.ProcessContext;
/**
* StateHandler Interceptor
*
* @author lorne.cl
*/
public interface StateHandlerInterceptor {
void preProcess(ProcessContext context) throws EngineExecutionException;
void postProcess(ProcessContext context, Exception e) throws EngineExecutionException;
boolean match(Class<? extends InterceptableStateHandler> clazz);
}

View File

@@ -0,0 +1,172 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.pcext;
import io.seata.common.exception.FrameworkErrorCode;
import io.seata.saga.engine.StateMachineConfig;
import io.seata.saga.engine.exception.EngineExecutionException;
import io.seata.saga.proctrl.Instruction;
import io.seata.saga.proctrl.ProcessContext;
import io.seata.saga.statelang.domain.DomainConstants;
import io.seata.saga.statelang.domain.State;
import io.seata.saga.statelang.domain.StateMachine;
import org.springframework.util.StringUtils;
/**
* State Instruction
*
* @author lorne.cl
* @see Instruction
*/
public class StateInstruction implements Instruction {
private String stateName;
private String stateMachineName;
private String tenantId;
private boolean end;
/**
* Temporary state node for running temporary nodes in the state machine
*/
private State temporaryState;
public StateInstruction() {
}
public StateInstruction(String stateMachineName, String tenantId) {
this.stateMachineName = stateMachineName;
this.tenantId = tenantId;
}
public State getState(ProcessContext context) {
if (getTemporaryState() != null) {
return temporaryState;
}
String stateName = getStateName();
String stateMachineName = getStateMachineName();
String tenantId = getTenantId();
if (StringUtils.isEmpty(stateMachineName)) {
throw new EngineExecutionException("StateMachineName is required", FrameworkErrorCode.ParameterRequired);
}
StateMachineConfig stateMachineConfig = (StateMachineConfig)context.getVariable(
DomainConstants.VAR_NAME_STATEMACHINE_CONFIG);
StateMachine stateMachine = stateMachineConfig.getStateMachineRepository().getStateMachine(stateMachineName,
tenantId);
if (stateMachine == null) {
throw new EngineExecutionException("StateMachine[" + stateMachineName + "] is not exist",
FrameworkErrorCode.ObjectNotExists);
}
if (StringUtils.isEmpty(stateName)) {
stateName = stateMachine.getStartState();
setStateName(stateName);
}
State state = stateMachine.getStates().get(stateName);
if (state == null) {
throw new EngineExecutionException("State[" + stateName + "] is not exist",
FrameworkErrorCode.ObjectNotExists);
}
return state;
}
/**
* Gets get state name.
*
* @return the get state name
*/
public String getStateName() {
return stateName;
}
/**
* Sets set state name.
*
* @param stateName the state name
*/
public void setStateName(String stateName) {
this.stateName = stateName;
}
/**
* Gets get state machine name.
*
* @return the get state machine name
*/
public String getStateMachineName() {
return stateMachineName;
}
/**
* Sets set state machine name.
*
* @param stateMachineName the state machine name
*/
public void setStateMachineName(String stateMachineName) {
this.stateMachineName = stateMachineName;
}
/**
* Is end boolean.
*
* @return the boolean
*/
public boolean isEnd() {
return end;
}
/**
* Sets set end.
*
* @param end the end
*/
public void setEnd(boolean end) {
this.end = end;
}
/**
* Gets get temporary state.
*
* @return the get temporary state
*/
public State getTemporaryState() {
return temporaryState;
}
/**
* Sets set temporary state.
*
* @param temporaryState the temporary state
*/
public void setTemporaryState(State temporaryState) {
this.temporaryState = temporaryState;
}
public String getTenantId() {
return tenantId;
}
public void setTenantId(String tenantId) {
this.tenantId = tenantId;
}
}

View File

@@ -0,0 +1,111 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.pcext;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import io.seata.common.exception.FrameworkException;
import io.seata.common.util.CollectionUtils;
import io.seata.saga.engine.pcext.handlers.ChoiceStateHandler;
import io.seata.saga.engine.pcext.handlers.CompensationTriggerStateHandler;
import io.seata.saga.engine.pcext.handlers.FailEndStateHandler;
import io.seata.saga.engine.pcext.handlers.LoopStartStateHandler;
import io.seata.saga.engine.pcext.handlers.ScriptTaskStateHandler;
import io.seata.saga.engine.pcext.handlers.ServiceTaskStateHandler;
import io.seata.saga.engine.pcext.handlers.SubStateMachineHandler;
import io.seata.saga.engine.pcext.handlers.SucceedEndStateHandler;
import io.seata.saga.proctrl.ProcessContext;
import io.seata.saga.proctrl.handler.ProcessHandler;
import io.seata.saga.statelang.domain.DomainConstants;
import io.seata.saga.statelang.domain.State;
/**
* StateMachine ProcessHandler
*
* @author lorne.cl
* @see ProcessHandler
*/
public class StateMachineProcessHandler implements ProcessHandler {
private final Map<String, StateHandler> stateHandlers = new ConcurrentHashMap<>();
@Override
public void process(ProcessContext context) throws FrameworkException {
StateInstruction instruction = context.getInstruction(StateInstruction.class);
State state = instruction.getState(context);
String stateType = state.getType();
StateHandler stateHandler = stateHandlers.get(stateType);
List<StateHandlerInterceptor> interceptors = null;
if (stateHandler instanceof InterceptableStateHandler) {
interceptors = ((InterceptableStateHandler)stateHandler).getInterceptors();
}
List<StateHandlerInterceptor> executedInterceptors = null;
Exception exception = null;
try {
if (CollectionUtils.isNotEmpty(interceptors)) {
executedInterceptors = new ArrayList<>(interceptors.size());
for (StateHandlerInterceptor interceptor : interceptors) {
executedInterceptors.add(interceptor);
interceptor.preProcess(context);
}
}
stateHandler.process(context);
} catch (Exception e) {
exception = e;
throw e;
} finally {
if (CollectionUtils.isNotEmpty(executedInterceptors)) {
for (int i = executedInterceptors.size() - 1; i >= 0; i--) {
StateHandlerInterceptor interceptor = executedInterceptors.get(i);
interceptor.postProcess(context, exception);
}
}
}
}
public void initDefaultHandlers() {
if (stateHandlers.isEmpty()) {
stateHandlers.put(DomainConstants.STATE_TYPE_SERVICE_TASK, new ServiceTaskStateHandler());
stateHandlers.put(DomainConstants.STATE_TYPE_SCRIPT_TASK, new ScriptTaskStateHandler());
stateHandlers.put(DomainConstants.STATE_TYPE_SUB_MACHINE_COMPENSATION, new ServiceTaskStateHandler());
stateHandlers.put(DomainConstants.STATE_TYPE_SUB_STATE_MACHINE, new SubStateMachineHandler());
stateHandlers.put(DomainConstants.STATE_TYPE_CHOICE, new ChoiceStateHandler());
stateHandlers.put(DomainConstants.STATE_TYPE_SUCCEED, new SucceedEndStateHandler());
stateHandlers.put(DomainConstants.STATE_TYPE_FAIL, new FailEndStateHandler());
stateHandlers.put(DomainConstants.STATE_TYPE_COMPENSATION_TRIGGER, new CompensationTriggerStateHandler());
stateHandlers.put(DomainConstants.STATE_TYPE_LOOP_START, new LoopStartStateHandler());
}
}
public Map<String, StateHandler> getStateHandlers() {
return stateHandlers;
}
public void setStateHandlers(Map<String, StateHandler> stateHandlers) {
this.stateHandlers.putAll(stateHandlers);
}
}

View File

@@ -0,0 +1,130 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.pcext;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import io.seata.common.exception.FrameworkException;
import io.seata.common.util.CollectionUtils;
import io.seata.saga.engine.StateMachineConfig;
import io.seata.saga.engine.pcext.routers.EndStateRouter;
import io.seata.saga.engine.pcext.routers.TaskStateRouter;
import io.seata.saga.engine.pcext.utils.EngineUtils;
import io.seata.saga.proctrl.Instruction;
import io.seata.saga.proctrl.ProcessContext;
import io.seata.saga.proctrl.ProcessRouter;
import io.seata.saga.statelang.domain.DomainConstants;
import io.seata.saga.statelang.domain.State;
import io.seata.saga.statelang.domain.StateMachine;
/**
* StateMachine ProcessRouter
*
* @author lorne.cl
* @see ProcessRouter
*/
public class StateMachineProcessRouter implements ProcessRouter {
private final Map<String, StateRouter> stateRouters = new ConcurrentHashMap<>();
@Override
public Instruction route(ProcessContext context) throws FrameworkException {
StateInstruction stateInstruction = context.getInstruction(StateInstruction.class);
State state;
if (stateInstruction.getTemporaryState() != null) {
state = stateInstruction.getTemporaryState();
stateInstruction.setTemporaryState(null);
} else {
StateMachineConfig stateMachineConfig = (StateMachineConfig)context.getVariable(
DomainConstants.VAR_NAME_STATEMACHINE_CONFIG);
StateMachine stateMachine = stateMachineConfig.getStateMachineRepository().getStateMachine(
stateInstruction.getStateMachineName(), stateInstruction.getTenantId());
state = stateMachine.getStates().get(stateInstruction.getStateName());
}
String stateType = state.getType();
StateRouter router = stateRouters.get(stateType);
Instruction instruction = null;
List<StateRouterInterceptor> interceptors = null;
if (router instanceof InterceptableStateRouter) {
interceptors = ((InterceptableStateRouter)router).getInterceptors();
}
List<StateRouterInterceptor> executedInterceptors = null;
Exception exception = null;
try {
if (CollectionUtils.isNotEmpty(interceptors)) {
executedInterceptors = new ArrayList<>(interceptors.size());
for (StateRouterInterceptor interceptor : interceptors) {
executedInterceptors.add(interceptor);
interceptor.preRoute(context, state);
}
}
instruction = router.route(context, state);
} catch (Exception e) {
exception = e;
throw e;
} finally {
if (CollectionUtils.isNotEmpty(executedInterceptors)) {
for (int i = executedInterceptors.size() - 1; i >= 0; i--) {
StateRouterInterceptor interceptor = executedInterceptors.get(i);
interceptor.postRoute(context, state, instruction, exception);
}
}
//if 'Succeed' or 'Fail' State did not configured, we must end the state machine
if (instruction == null && !stateInstruction.isEnd()) {
EngineUtils.endStateMachine(context);
}
}
return instruction;
}
public void initDefaultStateRouters() {
if (this.stateRouters.isEmpty()) {
TaskStateRouter taskStateRouter = new TaskStateRouter();
this.stateRouters.put(DomainConstants.STATE_TYPE_SERVICE_TASK, taskStateRouter);
this.stateRouters.put(DomainConstants.STATE_TYPE_SCRIPT_TASK, taskStateRouter);
this.stateRouters.put(DomainConstants.STATE_TYPE_CHOICE, taskStateRouter);
this.stateRouters.put(DomainConstants.STATE_TYPE_COMPENSATION_TRIGGER, taskStateRouter);
this.stateRouters.put(DomainConstants.STATE_TYPE_SUB_STATE_MACHINE, taskStateRouter);
this.stateRouters.put(DomainConstants.STATE_TYPE_SUB_MACHINE_COMPENSATION, taskStateRouter);
this.stateRouters.put(DomainConstants.STATE_TYPE_LOOP_START, taskStateRouter);
this.stateRouters.put(DomainConstants.STATE_TYPE_SUCCEED, new EndStateRouter());
this.stateRouters.put(DomainConstants.STATE_TYPE_FAIL, new EndStateRouter());
}
}
public Map<String, StateRouter> getStateRouters() {
return stateRouters;
}
public void setStateRouters(Map<String, StateRouter> stateRouters) {
this.stateRouters.putAll(stateRouters);
}
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.pcext;
import io.seata.saga.engine.exception.EngineExecutionException;
import io.seata.saga.proctrl.Instruction;
import io.seata.saga.proctrl.ProcessContext;
import io.seata.saga.statelang.domain.State;
/**
* StateRouter
*
* @author lorne.cl
*/
public interface StateRouter {
Instruction route(ProcessContext context, State state) throws EngineExecutionException;
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.pcext;
import io.seata.saga.engine.exception.EngineExecutionException;
import io.seata.saga.proctrl.Instruction;
import io.seata.saga.proctrl.ProcessContext;
import io.seata.saga.statelang.domain.State;
/**
* StateRouter Interceptor
*
* @author lorne.cl
* @see StateRouter
*/
public interface StateRouterInterceptor {
/**
* pre route
*
* @param context
* @param state
* @throws EngineExecutionException
*/
void preRoute(ProcessContext context, State state) throws EngineExecutionException;
/**
* post route
*
* @param context
* @param state
* @param instruction
* @param e
* @throws EngineExecutionException
*/
void postRoute(ProcessContext context, State state, Instruction instruction, Exception e)
throws EngineExecutionException;
boolean match(Class<? extends InterceptableStateRouter> clazz);
}

View File

@@ -0,0 +1,103 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.pcext.handlers;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import io.seata.common.exception.FrameworkErrorCode;
import io.seata.saga.engine.StateMachineConfig;
import io.seata.saga.engine.evaluation.Evaluator;
import io.seata.saga.engine.evaluation.EvaluatorFactory;
import io.seata.saga.engine.evaluation.EvaluatorFactoryManager;
import io.seata.saga.engine.exception.EngineExecutionException;
import io.seata.saga.engine.pcext.StateHandler;
import io.seata.saga.engine.pcext.StateInstruction;
import io.seata.saga.engine.pcext.utils.EngineUtils;
import io.seata.saga.engine.utils.ExceptionUtils;
import io.seata.saga.proctrl.ProcessContext;
import io.seata.saga.statelang.domain.ChoiceState;
import io.seata.saga.statelang.domain.DomainConstants;
import io.seata.saga.statelang.domain.StateMachineInstance;
import io.seata.saga.statelang.domain.impl.ChoiceStateImpl;
import org.springframework.util.StringUtils;
/**
* ChoiceState Handler
*
* @author lorne.cl
*/
public class ChoiceStateHandler implements StateHandler {
@Override
public void process(ProcessContext context) throws EngineExecutionException {
StateInstruction instruction = context.getInstruction(StateInstruction.class);
ChoiceStateImpl choiceState = (ChoiceStateImpl)instruction.getState(context);
Map<Object, String> choiceEvaluators = choiceState.getChoiceEvaluators();
if (choiceEvaluators == null) {
synchronized (choiceState) {
choiceEvaluators = choiceState.getChoiceEvaluators();
if (choiceEvaluators == null) {
List<ChoiceState.Choice> choices = choiceState.getChoices();
if (choices == null) {
choiceEvaluators = new LinkedHashMap<>(0);
} else {
choiceEvaluators = new LinkedHashMap<>(choices.size());
for (ChoiceState.Choice choice : choices) {
Evaluator evaluator = getEvaluatorFactory(context).createEvaluator(choice.getExpression());
choiceEvaluators.put(evaluator, choice.getNext());
}
}
choiceState.setChoiceEvaluators(choiceEvaluators);
}
}
}
Evaluator evaluator;
for (Map.Entry<Object, String> entry : choiceEvaluators.entrySet()) {
evaluator = (Evaluator)entry.getKey();
if (evaluator.evaluate(context.getVariables())) {
context.setVariable(DomainConstants.VAR_NAME_CURRENT_CHOICE, entry.getValue());
return;
}
}
if (StringUtils.isEmpty(choiceState.getDefault())) {
StateMachineInstance stateMachineInstance = (StateMachineInstance)context.getVariable(
DomainConstants.VAR_NAME_STATEMACHINE_INST);
EngineExecutionException exception = ExceptionUtils.createEngineExecutionException(FrameworkErrorCode.StateMachineNoChoiceMatched, "No choice matched, maybe it is a bug. Choice state name: " + choiceState.getName(), stateMachineInstance, null);
EngineUtils.failStateMachine(context, exception);
throw exception;
}
context.setVariable(DomainConstants.VAR_NAME_CURRENT_CHOICE, choiceState.getDefault());
}
public EvaluatorFactory getEvaluatorFactory(ProcessContext context) {
StateMachineConfig stateMachineConfig = (StateMachineConfig)context.getVariable(
DomainConstants.VAR_NAME_STATEMACHINE_CONFIG);
return stateMachineConfig.getEvaluatorFactoryManager().getEvaluatorFactory(
EvaluatorFactoryManager.EVALUATOR_TYPE_DEFAULT);
}
}

View File

@@ -0,0 +1,88 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.pcext.handlers;
import java.util.List;
import java.util.Stack;
import io.seata.common.util.CollectionUtils;
import io.seata.saga.engine.StateMachineConfig;
import io.seata.saga.engine.exception.EngineExecutionException;
import io.seata.saga.engine.pcext.StateHandler;
import io.seata.saga.engine.pcext.StateInstruction;
import io.seata.saga.engine.pcext.utils.CompensationHolder;
import io.seata.saga.engine.pcext.utils.EngineUtils;
import io.seata.saga.proctrl.ProcessContext;
import io.seata.saga.statelang.domain.DomainConstants;
import io.seata.saga.statelang.domain.ExecutionStatus;
import io.seata.saga.statelang.domain.StateInstance;
import io.seata.saga.statelang.domain.StateMachineInstance;
/**
* CompensationTriggerState Handler
* Start to execute compensation
*
* @author lorne.cl
*/
public class CompensationTriggerStateHandler implements StateHandler {
@Override
public void process(ProcessContext context) throws EngineExecutionException {
StateInstruction instruction = context.getInstruction(StateInstruction.class);
StateMachineInstance stateMachineInstance = (StateMachineInstance)context.getVariable(
DomainConstants.VAR_NAME_STATEMACHINE_INST);
StateMachineConfig stateMachineConfig = (StateMachineConfig)context.getVariable(
DomainConstants.VAR_NAME_STATEMACHINE_CONFIG);
List<StateInstance> stateInstanceList = stateMachineInstance.getStateList();
if (CollectionUtils.isEmpty(stateInstanceList)) {
stateInstanceList = stateMachineConfig.getStateLogStore().queryStateInstanceListByMachineInstanceId(
stateMachineInstance.getId());
}
List<StateInstance> stateListToBeCompensated = CompensationHolder.findStateInstListToBeCompensated(context,
stateInstanceList);
if (CollectionUtils.isNotEmpty(stateListToBeCompensated)) {
//Clear exceptions that occur during forward execution
Exception e = (Exception)context.removeVariable(DomainConstants.VAR_NAME_CURRENT_EXCEPTION);
if (e != null) {
stateMachineInstance.setException(e);
}
Stack<StateInstance> stateStackToBeCompensated = CompensationHolder.getCurrent(context, true)
.getStateStackNeedCompensation();
stateStackToBeCompensated.addAll(stateListToBeCompensated);
//If the forward running state is empty or running,
// it indicates that the compensation state is automatically initiated in the state machine,
// and the forward state needs to be changed to the UN state.
//If the forward status is not the two states, then the compensation operation should be initiated by
// server recovery,
// and the forward state should not be modified.
if (stateMachineInstance.getStatus() == null || ExecutionStatus.RU.equals(
stateMachineInstance.getStatus())) {
stateMachineInstance.setStatus(ExecutionStatus.UN);
}
//Record the status of the state machine as "compensating", and the subsequent routing logic will route
// to the compensation state
stateMachineInstance.setCompensationStatus(ExecutionStatus.RU);
context.setVariable(DomainConstants.VAR_NAME_CURRENT_COMPEN_TRIGGER_STATE, instruction.getState(context));
} else {
EngineUtils.endStateMachine(context);
}
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.pcext.handlers;
import java.util.Map;
import io.seata.saga.engine.exception.EngineExecutionException;
import io.seata.saga.engine.pcext.StateHandler;
import io.seata.saga.engine.pcext.StateInstruction;
import io.seata.saga.proctrl.ProcessContext;
import io.seata.saga.statelang.domain.DomainConstants;
import io.seata.saga.statelang.domain.FailEndState;
/**
* FailEndState Handler
*
* @author lorne.cl
*/
public class FailEndStateHandler implements StateHandler {
@Override
public void process(ProcessContext context) throws EngineExecutionException {
context.setVariable(DomainConstants.VAR_NAME_FAIL_END_STATE_FLAG, true);
StateInstruction instruction = context.getInstruction(StateInstruction.class);
FailEndState state = (FailEndState)instruction.getState(context);
Map<String, Object> contextVariables = (Map<String, Object>)context.getVariable(
DomainConstants.VAR_NAME_STATEMACHINE_CONTEXT);
contextVariables.put(DomainConstants.VAR_NAME_STATEMACHINE_ERROR_CODE, state.getErrorCode());
contextVariables.put(DomainConstants.VAR_NAME_STATEMACHINE_ERROR_MSG, state.getMessage());
}
}

View File

@@ -0,0 +1,165 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.pcext.handlers;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import io.seata.common.exception.FrameworkErrorCode;
import io.seata.common.util.StringUtils;
import io.seata.saga.engine.StateMachineConfig;
import io.seata.saga.engine.exception.EngineExecutionException;
import io.seata.saga.engine.pcext.StateHandler;
import io.seata.saga.engine.pcext.StateInstruction;
import io.seata.saga.engine.pcext.utils.EngineUtils;
import io.seata.saga.engine.pcext.utils.LoopContextHolder;
import io.seata.saga.engine.pcext.utils.LoopTaskUtils;
import io.seata.saga.proctrl.HierarchicalProcessContext;
import io.seata.saga.proctrl.ProcessContext;
import io.seata.saga.proctrl.impl.ProcessContextImpl;
import io.seata.saga.statelang.domain.DomainConstants;
import io.seata.saga.statelang.domain.StateMachineInstance;
import io.seata.saga.statelang.domain.TaskState.Loop;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Loop State Handler
* Start Loop Execution
*
* @author anselleeyy
*/
public class LoopStartStateHandler implements StateHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(LoopStartStateHandler.class);
private static final int AWAIT_TIMEOUT = 1000;
@Override
public void process(ProcessContext context) throws EngineExecutionException {
StateInstruction instruction = context.getInstruction(StateInstruction.class);
StateMachineInstance stateMachineInstance = (StateMachineInstance)context.getVariable(
DomainConstants.VAR_NAME_STATEMACHINE_INST);
StateMachineConfig stateMachineConfig = (StateMachineConfig)context.getVariable(
DomainConstants.VAR_NAME_STATEMACHINE_CONFIG);
instruction.setTemporaryState(null);
Loop loop = LoopTaskUtils.getLoopConfig(context, instruction.getState(context));
LoopContextHolder loopContextHolder = LoopContextHolder.getCurrent(context, true);
Semaphore semaphore = null;
int maxInstances = 0;
List<ProcessContext> loopContextList = new ArrayList<>();
if (null != loop) {
if (!stateMachineConfig.isEnableAsync() || null == stateMachineConfig.getAsyncProcessCtrlEventPublisher()) {
throw new EngineExecutionException(
"Asynchronous start is disabled. Loop execution will run asynchronous, please set "
+ "StateMachineConfig.enableAsync=true first.", FrameworkErrorCode.AsynchronousStartDisabled);
}
int totalInstances;
if (DomainConstants.OPERATION_NAME_FORWARD.equals(context.getVariable(DomainConstants.VAR_NAME_OPERATION_NAME))) {
LoopTaskUtils.reloadLoopContext(context, instruction.getState(context).getName());
totalInstances = loopContextHolder.getNrOfInstances().get() - loopContextHolder.getNrOfCompletedInstances().get();
} else {
LoopTaskUtils.createLoopCounterContext(context);
totalInstances = loopContextHolder.getNrOfInstances().get();
}
maxInstances = Math.min(loop.getParallel(), totalInstances);
semaphore = new Semaphore(maxInstances);
context.setVariable(DomainConstants.LOOP_SEMAPHORE, semaphore);
context.setVariable(DomainConstants.VAR_NAME_IS_LOOP_STATE, true);
// publish loop tasks
for (int i = 0; i < totalInstances; i++) {
try {
semaphore.acquire();
ProcessContextImpl tempContext;
// fail end inst should be forward without completion condition check
if (!loopContextHolder.getForwardCounterStack().isEmpty()) {
int failEndLoopCounter = loopContextHolder.getForwardCounterStack().pop();
tempContext = (ProcessContextImpl)LoopTaskUtils.createLoopEventContext(context, failEndLoopCounter);
} else if (loopContextHolder.isFailEnd() || LoopTaskUtils.isCompletionConditionSatisfied(context)) {
semaphore.release();
break;
} else {
tempContext = (ProcessContextImpl)LoopTaskUtils.createLoopEventContext(context, -1);
}
if (DomainConstants.OPERATION_NAME_FORWARD.equals(context.getVariable(DomainConstants.VAR_NAME_OPERATION_NAME))) {
((HierarchicalProcessContext)context).setVariableLocally(
DomainConstants.VAR_NAME_IS_FOR_SUB_STATMACHINE_FORWARD, LoopTaskUtils.isForSubStateMachineForward(tempContext));
}
stateMachineConfig.getAsyncProcessCtrlEventPublisher().publish(tempContext);
loopContextHolder.getNrOfActiveInstances().incrementAndGet();
loopContextList.add(tempContext);
} catch (InterruptedException e) {
LOGGER.error("try execute loop task for State: [{}] is interrupted, message: [{}]",
instruction.getStateName(), e.getMessage());
throw new EngineExecutionException(e);
}
}
} else {
LOGGER.warn("Loop config of State [{}] is illegal, will execute as normal", instruction.getStateName());
instruction.setTemporaryState(instruction.getState(context));
}
try {
if (null != semaphore) {
boolean isFinished = false;
while (!isFinished) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("wait {}ms for loop state [{}] finish", AWAIT_TIMEOUT, instruction.getStateName());
}
isFinished = semaphore.tryAcquire(maxInstances, AWAIT_TIMEOUT, TimeUnit.MILLISECONDS);
}
if (loopContextList.size() > 0) {
LoopTaskUtils.putContextToParent(context, loopContextList, instruction.getState(context));
}
}
} catch (InterruptedException e) {
LOGGER.error("State: [{}] wait loop execution complete is interrupted, message: [{}]",
instruction.getStateName(), e.getMessage());
throw new EngineExecutionException(e);
} finally {
context.removeVariable(DomainConstants.LOOP_SEMAPHORE);
context.removeVariable(DomainConstants.VAR_NAME_IS_LOOP_STATE);
LoopContextHolder.clearCurrent(context);
}
if (loopContextHolder.isFailEnd()) {
String currentExceptionRoute = LoopTaskUtils.decideCurrentExceptionRoute(loopContextList, stateMachineInstance.getStateMachine());
if (StringUtils.isNotBlank(currentExceptionRoute)) {
((HierarchicalProcessContext)context).setVariableLocally(DomainConstants.VAR_NAME_CURRENT_EXCEPTION_ROUTE, currentExceptionRoute);
} else {
for (ProcessContext processContext : loopContextList) {
if (processContext.hasVariable(DomainConstants.VAR_NAME_CURRENT_EXCEPTION)) {
Exception exception = (Exception)processContext.getVariable(DomainConstants.VAR_NAME_CURRENT_EXCEPTION);
EngineUtils.failStateMachine(context, exception);
break;
}
}
}
}
}
}

View File

@@ -0,0 +1,152 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.pcext.handlers;
import io.seata.common.exception.FrameworkErrorCode;
import io.seata.common.util.CollectionUtils;
import io.seata.saga.engine.StateMachineConfig;
import io.seata.saga.engine.exception.EngineExecutionException;
import io.seata.saga.engine.pcext.InterceptableStateHandler;
import io.seata.saga.engine.pcext.StateHandler;
import io.seata.saga.engine.pcext.StateHandlerInterceptor;
import io.seata.saga.engine.pcext.StateInstruction;
import io.seata.saga.engine.pcext.utils.EngineUtils;
import io.seata.saga.proctrl.HierarchicalProcessContext;
import io.seata.saga.proctrl.ProcessContext;
import io.seata.saga.statelang.domain.DomainConstants;
import io.seata.saga.statelang.domain.impl.ScriptTaskStateImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.script.Bindings;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.SimpleBindings;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* ScriptTaskState Handler
*
* @author lorne.cl
*/
public class ScriptTaskStateHandler implements StateHandler, InterceptableStateHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(ScriptTaskStateHandler.class);
private List<StateHandlerInterceptor> interceptors = new ArrayList<>();
private volatile Map<String, ScriptEngine> scriptEngineCache = new ConcurrentHashMap<>();
@Override
public void process(ProcessContext context) throws EngineExecutionException {
StateInstruction instruction = context.getInstruction(StateInstruction.class);
ScriptTaskStateImpl state = (ScriptTaskStateImpl) instruction.getState(context);
String scriptType = state.getScriptType();
String scriptContent = state.getScriptContent();
Object result;
try {
List<Object> input = (List<Object>) context.getVariable(DomainConstants.VAR_NAME_INPUT_PARAMS);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(">>>>>>>>>>>>>>>>>>>>>> Start to execute ScriptTaskState[{}], ScriptType[{}], Input:{}",
state.getName(), scriptType, input);
}
StateMachineConfig stateMachineConfig = (StateMachineConfig) context.getVariable(
DomainConstants.VAR_NAME_STATEMACHINE_CONFIG);
ScriptEngine scriptEngine = getScriptEngineFromCache(scriptType, stateMachineConfig.getScriptEngineManager());
if (scriptEngine == null) {
throw new EngineExecutionException("No such ScriptType[" + scriptType + "]",
FrameworkErrorCode.ObjectNotExists);
}
Bindings bindings = null;
Map<String, Object> inputMap = null;
if (CollectionUtils.isNotEmpty(input) && input.get(0) instanceof Map) {
inputMap = (Map<String, Object>) input.get(0);
}
List<Object> inputExps = state.getInput();
if (CollectionUtils.isNotEmpty(inputExps) && inputExps.get(0) instanceof Map) {
Map<String, Object> inputExpMap = (Map<String, Object>) inputExps.get(0);
if (inputExpMap.size() > 0) {
bindings = new SimpleBindings();
for (String property : inputExpMap.keySet()) {
if (inputMap != null && inputMap.containsKey(property)) {
bindings.put(property, inputMap.get(property));
} else {
//if we do not bind the null value property, groovy will throw MissingPropertyException
bindings.put(property, null);
}
}
}
}
if (bindings != null) {
result = scriptEngine.eval(scriptContent, bindings);
}
else {
result = scriptEngine.eval(scriptContent);
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("<<<<<<<<<<<<<<<<<<<<<< ScriptTaskState[{}], ScriptType[{}], Execute finish. result: {}",
state.getName(), scriptType, result);
}
if (result != null) {
((HierarchicalProcessContext) context).setVariableLocally(DomainConstants.VAR_NAME_OUTPUT_PARAMS,
result);
}
} catch (Throwable e) {
LOGGER.error("<<<<<<<<<<<<<<<<<<<<<< ScriptTaskState[{}], ScriptTaskState[{}] Execute failed.",
state.getName(), scriptType, e);
((HierarchicalProcessContext) context).setVariableLocally(DomainConstants.VAR_NAME_CURRENT_EXCEPTION, e);
EngineUtils.handleException(context, state, e);
}
}
protected ScriptEngine getScriptEngineFromCache(String scriptType, ScriptEngineManager scriptEngineManager) {
return CollectionUtils.computeIfAbsent(scriptEngineCache, scriptType,
key -> scriptEngineManager.getEngineByName(scriptType));
}
@Override
public List<StateHandlerInterceptor> getInterceptors() {
return interceptors;
}
@Override
public void addInterceptor(StateHandlerInterceptor interceptor) {
if (interceptors != null && !interceptors.contains(interceptor)) {
interceptors.add(interceptor);
}
}
public void setInterceptors(List<StateHandlerInterceptor> interceptors) {
this.interceptors = interceptors;
}
}

View File

@@ -0,0 +1,191 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.pcext.handlers;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import io.seata.common.exception.FrameworkErrorCode;
import io.seata.common.util.CollectionUtils;
import io.seata.saga.engine.StateMachineConfig;
import io.seata.saga.engine.StateMachineEngine;
import io.seata.saga.engine.exception.EngineExecutionException;
import io.seata.saga.engine.invoker.ServiceInvoker;
import io.seata.saga.engine.pcext.InterceptableStateHandler;
import io.seata.saga.engine.pcext.StateHandler;
import io.seata.saga.engine.pcext.StateHandlerInterceptor;
import io.seata.saga.engine.pcext.StateInstruction;
import io.seata.saga.engine.pcext.utils.EngineUtils;
import io.seata.saga.proctrl.HierarchicalProcessContext;
import io.seata.saga.proctrl.ProcessContext;
import io.seata.saga.statelang.domain.CompensateSubStateMachineState;
import io.seata.saga.statelang.domain.DomainConstants;
import io.seata.saga.statelang.domain.ExecutionStatus;
import io.seata.saga.statelang.domain.ServiceTaskState;
import io.seata.saga.statelang.domain.StateInstance;
import io.seata.saga.statelang.domain.StateMachineInstance;
import io.seata.saga.statelang.domain.impl.ServiceTaskStateImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContextAware;
import org.springframework.util.StringUtils;
/**
* ServiceTaskState Handler
*
* @author lorne.cl
*/
public class ServiceTaskStateHandler implements StateHandler, InterceptableStateHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(ServiceTaskStateHandler.class);
private List<StateHandlerInterceptor> interceptors = new ArrayList<>();
@Override
public void process(ProcessContext context) throws EngineExecutionException {
StateInstruction instruction = context.getInstruction(StateInstruction.class);
ServiceTaskStateImpl state = (ServiceTaskStateImpl) instruction.getState(context);
String serviceName = state.getServiceName();
String methodName = state.getServiceMethod();
StateInstance stateInstance = (StateInstance) context.getVariable(DomainConstants.VAR_NAME_STATE_INST);
Object result;
try {
List<Object> input = (List<Object>) context.getVariable(DomainConstants.VAR_NAME_INPUT_PARAMS);
//Set the current task execution status to RU (Running)
stateInstance.setStatus(ExecutionStatus.RU);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(">>>>>>>>>>>>>>>>>>>>>> Start to execute State[{}], ServiceName[{}], Method[{}], Input:{}",
state.getName(), serviceName, methodName, input);
}
if (state instanceof CompensateSubStateMachineState) {
//If it is the compensation of the substate machine,
// directly call the state machine's compensate method
result = compensateSubStateMachine(context, state, input, stateInstance,
(StateMachineEngine) context.getVariable(DomainConstants.VAR_NAME_STATEMACHINE_ENGINE));
} else {
StateMachineConfig stateMachineConfig = (StateMachineConfig) context.getVariable(
DomainConstants.VAR_NAME_STATEMACHINE_CONFIG);
ServiceInvoker serviceInvoker = stateMachineConfig.getServiceInvokerManager().getServiceInvoker(
state.getServiceType());
if (serviceInvoker == null) {
throw new EngineExecutionException("No such ServiceInvoker[" + state.getServiceType() + "]",
FrameworkErrorCode.ObjectNotExists);
}
if (serviceInvoker instanceof ApplicationContextAware) {
((ApplicationContextAware) serviceInvoker).setApplicationContext(
stateMachineConfig.getApplicationContext());
}
result = serviceInvoker.invoke(state, input.toArray());
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("<<<<<<<<<<<<<<<<<<<<<< State[{}], ServiceName[{}], Method[{}] Execute finish. result: {}",
state.getName(), serviceName, methodName, result);
}
if (result != null) {
stateInstance.setOutputParams(result);
((HierarchicalProcessContext) context).setVariableLocally(DomainConstants.VAR_NAME_OUTPUT_PARAMS,
result);
}
} catch (Throwable e) {
LOGGER.error("<<<<<<<<<<<<<<<<<<<<<< State[{}], ServiceName[{}], Method[{}] Execute failed.",
state.getName(), serviceName, methodName, e);
((HierarchicalProcessContext) context).setVariableLocally(DomainConstants.VAR_NAME_CURRENT_EXCEPTION, e);
EngineUtils.handleException(context, state, e);
}
}
private Object compensateSubStateMachine(ProcessContext context, ServiceTaskState state, Object input,
StateInstance stateInstance, StateMachineEngine engine) {
String subStateMachineParentId = (String) context.getVariable(
state.getName() + DomainConstants.VAR_NAME_SUB_MACHINE_PARENT_ID);
if (StringUtils.isEmpty(subStateMachineParentId)) {
throw new EngineExecutionException("sub statemachine parentId is required",
FrameworkErrorCode.ObjectNotExists);
}
StateMachineConfig stateMachineConfig = (StateMachineConfig) context.getVariable(
DomainConstants.VAR_NAME_STATEMACHINE_CONFIG);
List<StateMachineInstance> subInst = stateMachineConfig.getStateLogStore().queryStateMachineInstanceByParentId(
subStateMachineParentId);
if (CollectionUtils.isEmpty(subInst)) {
throw new EngineExecutionException(
"cannot find sub statemachine instance by parentId:" + subStateMachineParentId,
FrameworkErrorCode.ObjectNotExists);
}
String subStateMachineInstId = subInst.get(0).getId();
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(">>>>>>>>>>>>>>>>>>>>>> Start to compensate sub statemachine [id:{}]", subStateMachineInstId);
}
Map<String, Object> startParams = new HashMap<>(0);
if (input instanceof List) {
List<Object> listInputParams = (List<Object>) input;
if (listInputParams.size() > 0) {
startParams = (Map<String, Object>) listInputParams.get(0);
}
} else if (input instanceof Map) {
startParams = (Map<String, Object>) input;
}
StateMachineInstance compensateInst = engine.compensate(subStateMachineInstId, startParams);
stateInstance.setStatus(compensateInst.getCompensationStatus());
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
"<<<<<<<<<<<<<<<<<<<<<< Compensate sub statemachine [id:{}] finished with status[{}], "
+ "compensateState[{}]",
subStateMachineInstId, compensateInst.getStatus(), compensateInst.getCompensationStatus());
}
return compensateInst.getEndParams();
}
@Override
public List<StateHandlerInterceptor> getInterceptors() {
return interceptors;
}
@Override
public void addInterceptor(StateHandlerInterceptor interceptor) {
if (interceptors != null && !interceptors.contains(interceptor)) {
interceptors.add(interceptor);
}
}
public void setInterceptors(List<StateHandlerInterceptor> interceptors) {
this.interceptors = interceptors;
}
}

View File

@@ -0,0 +1,213 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.pcext.handlers;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import io.seata.common.exception.FrameworkErrorCode;
import io.seata.saga.engine.StateMachineConfig;
import io.seata.saga.engine.StateMachineEngine;
import io.seata.saga.engine.exception.EngineExecutionException;
import io.seata.saga.engine.exception.ForwardInvalidException;
import io.seata.saga.engine.pcext.InterceptableStateHandler;
import io.seata.saga.engine.pcext.StateHandler;
import io.seata.saga.engine.pcext.StateHandlerInterceptor;
import io.seata.saga.engine.pcext.StateInstruction;
import io.seata.saga.engine.pcext.utils.EngineUtils;
import io.seata.saga.engine.store.StateLogStore;
import io.seata.saga.proctrl.ProcessContext;
import io.seata.saga.statelang.domain.DomainConstants;
import io.seata.saga.statelang.domain.ExecutionStatus;
import io.seata.saga.statelang.domain.StateInstance;
import io.seata.saga.statelang.domain.StateMachineInstance;
import io.seata.saga.statelang.domain.SubStateMachine;
import io.seata.saga.statelang.domain.impl.SubStateMachineImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
/**
* SubStateMachine Handler
*
* @author lorne.cl
*/
public class SubStateMachineHandler implements StateHandler, InterceptableStateHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(SubStateMachineHandler.class);
private List<StateHandlerInterceptor> interceptors = new ArrayList<>();
private static ExecutionStatus decideStatus(StateMachineInstance stateMachineInstance, boolean isForward) {
if (isForward && ExecutionStatus.SU.equals(stateMachineInstance.getStatus())) {
return ExecutionStatus.SU;
} else if (stateMachineInstance.getCompensationStatus() == null || ExecutionStatus.FA.equals(
stateMachineInstance.getCompensationStatus())) {
return stateMachineInstance.getStatus();
} else if (ExecutionStatus.SU.equals(stateMachineInstance.getCompensationStatus())) {
return ExecutionStatus.FA;
} else {
return ExecutionStatus.UN;
}
}
@Override
public void process(ProcessContext context) throws EngineExecutionException {
StateInstruction instruction = context.getInstruction(StateInstruction.class);
SubStateMachineImpl subStateMachine = (SubStateMachineImpl)instruction.getState(context);
StateMachineEngine engine = (StateMachineEngine)context.getVariable(
DomainConstants.VAR_NAME_STATEMACHINE_ENGINE);
StateMachineInstance stateMachineInstance = (StateMachineInstance)context.getVariable(
DomainConstants.VAR_NAME_STATEMACHINE_INST);
StateInstance stateInstance = (StateInstance)context.getVariable(DomainConstants.VAR_NAME_STATE_INST);
Object inputParamsObj = context.getVariable(DomainConstants.VAR_NAME_INPUT_PARAMS);
Map<String, Object> startParams = new HashMap<>(0);
if (inputParamsObj instanceof List) {
List<Object> listInputParams = (List<Object>)inputParamsObj;
if (listInputParams.size() > 0) {
startParams = (Map<String, Object>)listInputParams.get(0);
}
} else if (inputParamsObj instanceof Map) {
startParams = (Map<String, Object>)inputParamsObj;
}
startParams.put(DomainConstants.VAR_NAME_PARENT_ID, EngineUtils.generateParentId(stateInstance));
try {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(">>>>>>>>>>>>>>>>>>>>>> Start to execute SubStateMachine [{}] by state[{}]",
subStateMachine.getStateMachineName(), subStateMachine.getName());
}
StateMachineInstance subStateMachineInstance = callSubStateMachine(startParams, engine, context,
stateInstance, subStateMachine);
Map<String, Object> outputParams = subStateMachineInstance.getEndParams();
boolean isForward = DomainConstants.OPERATION_NAME_FORWARD.equals(
context.getVariable(DomainConstants.VAR_NAME_OPERATION_NAME));
ExecutionStatus callSubMachineStatus = decideStatus(subStateMachineInstance, isForward);
stateInstance.setStatus(callSubMachineStatus);
outputParams.put(DomainConstants.VAR_NAME_SUB_STATEMACHINE_EXEC_STATUE, callSubMachineStatus.toString());
context.setVariable(DomainConstants.VAR_NAME_OUTPUT_PARAMS, outputParams);
stateInstance.setOutputParams(outputParams);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
"<<<<<<<<<<<<<<<<<<<<<< SubStateMachine[{}] execute finish with status[{}], compensateStatus[{}]",
subStateMachine.getStateMachineName(), subStateMachineInstance.getStatus(),
subStateMachineInstance.getCompensationStatus());
}
} catch (Exception e) {
LOGGER.error("SubStateMachine[{}] execute failed by state[name:{}]", subStateMachine.getStateMachineName(),
subStateMachine.getName(), e);
if (e instanceof ForwardInvalidException) {
String retriedId = stateInstance.getStateIdRetriedFor();
StateInstance stateToBeRetried = null;
for (StateInstance stateInst : stateMachineInstance.getStateList()) {
if (retriedId.equals(stateInst.getId())) {
stateToBeRetried = stateInst;
break;
}
}
if (stateToBeRetried != null) {
stateInstance.setStatus(stateToBeRetried.getStatus());
}
}
context.setVariable(DomainConstants.VAR_NAME_CURRENT_EXCEPTION, e);
EngineUtils.handleException(context, subStateMachine, e);
}
}
private StateMachineInstance callSubStateMachine(Map<String, Object> startParams, StateMachineEngine engine,
ProcessContext context, StateInstance stateInstance,
SubStateMachine subStateMachine) {
if (!Boolean.TRUE.equals(context.getVariable(DomainConstants.VAR_NAME_IS_FOR_SUB_STATMACHINE_FORWARD))) {
return startNewStateMachine(startParams, engine, stateInstance, subStateMachine);
} else {
context.removeVariable(DomainConstants.VAR_NAME_IS_FOR_SUB_STATMACHINE_FORWARD);
return forwardStateMachine(startParams, engine, context, stateInstance, subStateMachine);
}
}
private StateMachineInstance startNewStateMachine(Map<String, Object> startParams, StateMachineEngine engine,
StateInstance stateInstance, SubStateMachine subStateMachine) {
StateMachineInstance subStateMachineInstance;
if (stateInstance.getBusinessKey() != null) {
subStateMachineInstance = engine.startWithBusinessKey(subStateMachine.getStateMachineName(),
stateInstance.getStateMachineInstance().getTenantId(), stateInstance.getBusinessKey(), startParams);
} else {
subStateMachineInstance = engine.start(subStateMachine.getStateMachineName(),
stateInstance.getStateMachineInstance().getTenantId(), startParams);
}
return subStateMachineInstance;
}
private StateMachineInstance forwardStateMachine(Map<String, Object> startParams, StateMachineEngine engine,
ProcessContext context, StateInstance stateInstance,
SubStateMachine subStateMachine) {
StateMachineConfig stateMachineConfig = (StateMachineConfig)context.getVariable(
DomainConstants.VAR_NAME_STATEMACHINE_CONFIG);
StateLogStore statePersister = stateMachineConfig.getStateLogStore();
if (statePersister == null) {
throw new ForwardInvalidException("StatePersister is not configured", FrameworkErrorCode.ObjectNotExists);
}
StateInstance originalStateInst = stateInstance;
do {
originalStateInst = statePersister.getStateInstance(originalStateInst.getStateIdRetriedFor(),
originalStateInst.getMachineInstanceId());
} while (StringUtils.hasText(originalStateInst.getStateIdRetriedFor()));
List<StateMachineInstance> subInst = statePersister.queryStateMachineInstanceByParentId(
EngineUtils.generateParentId(originalStateInst));
if (subInst.size() > 0) {
String subInstId = subInst.get(0).getId();
return engine.forward(subInstId, startParams);
} else {
originalStateInst.setStateMachineInstance(stateInstance.getStateMachineInstance());
return startNewStateMachine(startParams, engine, originalStateInst, subStateMachine);
}
}
@Override
public List<StateHandlerInterceptor> getInterceptors() {
return interceptors;
}
@Override
public void addInterceptor(StateHandlerInterceptor interceptor) {
if (interceptors != null && !interceptors.contains(interceptor)) {
interceptors.add(interceptor);
}
}
public void setInterceptors(List<StateHandlerInterceptor> interceptors) {
this.interceptors = interceptors;
}
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.pcext.handlers;
import io.seata.saga.engine.exception.EngineExecutionException;
import io.seata.saga.engine.pcext.StateHandler;
import io.seata.saga.proctrl.ProcessContext;
/**
* SucceedEndState Handler
*
* @author lorne.cl
*/
public class SucceedEndStateHandler implements StateHandler {
@Override
public void process(ProcessContext context) throws EngineExecutionException {
//Do Nothing
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.pcext.interceptors;
import io.seata.common.loader.LoadLevel;
import io.seata.saga.engine.exception.EngineExecutionException;
import io.seata.saga.engine.pcext.InterceptableStateRouter;
import io.seata.saga.engine.pcext.StateRouterInterceptor;
import io.seata.saga.engine.pcext.routers.EndStateRouter;
import io.seata.saga.engine.pcext.utils.EngineUtils;
import io.seata.saga.proctrl.Instruction;
import io.seata.saga.proctrl.ProcessContext;
import io.seata.saga.statelang.domain.State;
/**
* EndStateRouter Interceptor
*
* @author lorne.cl
*/
@LoadLevel(name = "EndState", order = 100)
public class EndStateRouterInterceptor implements StateRouterInterceptor {
@Override
public void preRoute(ProcessContext context, State state) throws EngineExecutionException {
//Do Nothing
}
@Override
public void postRoute(ProcessContext context, State state, Instruction instruction, Exception e)
throws EngineExecutionException {
EngineUtils.endStateMachine(context);
}
@Override
public boolean match(Class<? extends InterceptableStateRouter> clazz) {
return clazz != null && EndStateRouter.class.isAssignableFrom(clazz);
}
}

View File

@@ -0,0 +1,138 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.pcext.interceptors;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Semaphore;
import io.seata.common.loader.LoadLevel;
import io.seata.saga.engine.exception.EngineExecutionException;
import io.seata.saga.engine.pcext.InterceptableStateHandler;
import io.seata.saga.engine.pcext.StateHandlerInterceptor;
import io.seata.saga.engine.pcext.StateInstruction;
import io.seata.saga.engine.pcext.handlers.ServiceTaskStateHandler;
import io.seata.saga.engine.pcext.handlers.SubStateMachineHandler;
import io.seata.saga.engine.pcext.utils.CompensationHolder;
import io.seata.saga.engine.pcext.utils.EngineUtils;
import io.seata.saga.engine.pcext.utils.LoopContextHolder;
import io.seata.saga.engine.pcext.utils.LoopTaskUtils;
import io.seata.saga.proctrl.HierarchicalProcessContext;
import io.seata.saga.proctrl.ProcessContext;
import io.seata.saga.statelang.domain.DomainConstants;
import io.seata.saga.statelang.domain.ExecutionStatus;
import io.seata.saga.statelang.domain.StateInstance;
import io.seata.saga.statelang.domain.TaskState.Loop;
import io.seata.saga.statelang.domain.impl.AbstractTaskState;
/**
* State Interceptor For ServiceTask, SubStateMachine, ScriptTask With Loop Attribute
*
* @author anselleeyy
*/
@LoadLevel(name = "LoopTask", order = 90)
public class LoopTaskHandlerInterceptor implements StateHandlerInterceptor {
@Override
public boolean match(Class<? extends InterceptableStateHandler> clazz) {
return clazz != null &&
(ServiceTaskStateHandler.class.isAssignableFrom(clazz)
|| SubStateMachineHandler.class.isAssignableFrom(clazz)
|| ScriptTaskHandlerInterceptor.class.isAssignableFrom(clazz));
}
@Override
public void preProcess(ProcessContext context) throws EngineExecutionException {
if (context.hasVariable(DomainConstants.VAR_NAME_IS_LOOP_STATE)) {
StateInstruction instruction = context.getInstruction(StateInstruction.class);
AbstractTaskState currentState = (AbstractTaskState)instruction.getState(context);
int loopCounter;
Loop loop;
// get loop config
if (context.hasVariable(DomainConstants.VAR_NAME_CURRENT_COMPEN_TRIGGER_STATE)) {
// compensate condition should get stateToBeCompensated 's config
CompensationHolder compensationHolder = CompensationHolder.getCurrent(context, true);
StateInstance stateToBeCompensated = compensationHolder.getStatesNeedCompensation().get(currentState.getName());
AbstractTaskState compensateState = (AbstractTaskState)stateToBeCompensated.getStateMachineInstance()
.getStateMachine().getState(EngineUtils.getOriginStateName(stateToBeCompensated));
loop = compensateState.getLoop();
loopCounter = LoopTaskUtils.reloadLoopCounter(stateToBeCompensated.getName());
} else {
loop = currentState.getLoop();
loopCounter = (int)context.getVariable(DomainConstants.LOOP_COUNTER);
}
Collection collection = LoopContextHolder.getCurrent(context, true).getCollection();
Map<String, Object> contextVariables = (Map<String, Object>)context.getVariable(
DomainConstants.VAR_NAME_STATEMACHINE_CONTEXT);
Map<String, Object> copyContextVariables = new ConcurrentHashMap<>(contextVariables);
copyContextVariables.put(loop.getElementIndexName(), loopCounter);
copyContextVariables.put(loop.getElementVariableName(), iterator(collection, loopCounter));
((HierarchicalProcessContext)context).setVariableLocally(DomainConstants.VAR_NAME_STATEMACHINE_CONTEXT, copyContextVariables);
}
}
@Override
public void postProcess(ProcessContext context, Exception e) throws EngineExecutionException {
if (context.hasVariable(DomainConstants.VAR_NAME_IS_LOOP_STATE)) {
StateInstance stateInstance = (StateInstance)context.getVariable(DomainConstants.VAR_NAME_STATE_INST);
if (null != stateInstance && !LoopContextHolder.getCurrent(context, true).isFailEnd()) {
if (!ExecutionStatus.SU.equals(stateInstance.getStatus())) {
LoopContextHolder.getCurrent(context, true).setFailEnd(true);
}
}
Exception exp = (Exception)((HierarchicalProcessContext)context).getVariableLocally(DomainConstants.VAR_NAME_CURRENT_EXCEPTION);
if (exp == null) {
exp = e;
}
if (null != e) {
if (context.hasVariable(DomainConstants.LOOP_SEMAPHORE)) {
Semaphore semaphore = (Semaphore)context.getVariable(DomainConstants.LOOP_SEMAPHORE);
semaphore.release();
}
}
if (null != exp) {
LoopContextHolder.getCurrent(context, true).setFailEnd(true);
} else {
LoopContextHolder.getCurrent(context, true).getNrOfCompletedInstances().incrementAndGet();
}
LoopContextHolder.getCurrent(context, true).getNrOfActiveInstances().decrementAndGet();
}
}
private Object iterator(Collection collection, int loopCounter) {
Iterator iterator = collection.iterator();
int index = 0;
Object value = null;
while (index <= loopCounter) {
value = iterator.next();
index += 1;
}
return value;
}
}

View File

@@ -0,0 +1,140 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.pcext.interceptors;
import io.seata.common.exception.FrameworkErrorCode;
import io.seata.common.loader.LoadLevel;
import io.seata.common.util.CollectionUtils;
import io.seata.saga.engine.StateMachineConfig;
import io.seata.saga.engine.exception.EngineExecutionException;
import io.seata.saga.engine.pcext.InterceptableStateHandler;
import io.seata.saga.engine.pcext.StateHandlerInterceptor;
import io.seata.saga.engine.pcext.StateInstruction;
import io.seata.saga.engine.pcext.handlers.ScriptTaskStateHandler;
import io.seata.saga.engine.pcext.utils.EngineUtils;
import io.seata.saga.engine.pcext.utils.ParameterUtils;
import io.seata.saga.engine.utils.ExceptionUtils;
import io.seata.saga.proctrl.HierarchicalProcessContext;
import io.seata.saga.proctrl.ProcessContext;
import io.seata.saga.statelang.domain.DomainConstants;
import io.seata.saga.statelang.domain.StateMachineInstance;
import io.seata.saga.statelang.domain.impl.ScriptTaskStateImpl;
import java.util.List;
import java.util.Map;
/**
* StateInterceptor for ScriptTask
*
* @author lorne.cl
*/
@LoadLevel(name = "ScriptTask", order = 100)
public class ScriptTaskHandlerInterceptor implements StateHandlerInterceptor {
@Override
public boolean match(Class<? extends InterceptableStateHandler> clazz) {
return clazz != null &&
ScriptTaskStateHandler.class.isAssignableFrom(clazz);
}
@Override
public void preProcess(ProcessContext context) throws EngineExecutionException {
StateInstruction instruction = context.getInstruction(StateInstruction.class);
StateMachineInstance stateMachineInstance = (StateMachineInstance)context.getVariable(
DomainConstants.VAR_NAME_STATEMACHINE_INST);
StateMachineConfig stateMachineConfig = (StateMachineConfig)context.getVariable(
DomainConstants.VAR_NAME_STATEMACHINE_CONFIG);
Map<String, Object> contextVariables = (Map<String, Object>)context.getVariable(
DomainConstants.VAR_NAME_STATEMACHINE_CONTEXT);
ScriptTaskStateImpl state = (ScriptTaskStateImpl)instruction.getState(context);
List<Object> serviceInputParams = null;
if (contextVariables != null) {
try {
serviceInputParams = ParameterUtils.createInputParams(stateMachineConfig.getExpressionFactoryManager(), null,
state, contextVariables);
} catch (Exception e) {
String message = "Task [" + state.getName()
+ "] input parameters assign failed, please check 'Input' expression:" + e.getMessage();
EngineExecutionException exception = ExceptionUtils.createEngineExecutionException(e,
FrameworkErrorCode.VariablesAssignError, message, stateMachineInstance, state.getName());
EngineUtils.failStateMachine(context, exception);
throw exception;
}
}
((HierarchicalProcessContext)context).setVariableLocally(DomainConstants.VAR_NAME_INPUT_PARAMS,
serviceInputParams);
}
@Override
public void postProcess(ProcessContext context, Exception exp) throws EngineExecutionException {
StateInstruction instruction = context.getInstruction(StateInstruction.class);
ScriptTaskStateImpl state = (ScriptTaskStateImpl)instruction.getState(context);
StateMachineInstance stateMachineInstance = (StateMachineInstance)context.getVariable(
DomainConstants.VAR_NAME_STATEMACHINE_INST);
StateMachineConfig stateMachineConfig = (StateMachineConfig)context.getVariable(
DomainConstants.VAR_NAME_STATEMACHINE_CONFIG);
if (exp == null) {
exp = (Exception)context.getVariable(DomainConstants.VAR_NAME_CURRENT_EXCEPTION);
}
Map<String, Object> contextVariables = (Map<String, Object>)context.getVariable(
DomainConstants.VAR_NAME_STATEMACHINE_CONTEXT);
Object serviceOutputParams = context.getVariable(DomainConstants.VAR_NAME_OUTPUT_PARAMS);
if (serviceOutputParams != null) {
try {
Map<String, Object> outputVariablesToContext = ParameterUtils.createOutputParams(
stateMachineConfig.getExpressionFactoryManager(), state, serviceOutputParams);
if (CollectionUtils.isNotEmpty(outputVariablesToContext)) {
contextVariables.putAll(outputVariablesToContext);
}
} catch (Exception e) {
String message = "Task [" + state.getName()
+ "] output parameters assign failed, please check 'Output' expression:" + e.getMessage();
EngineExecutionException exception = ExceptionUtils.createEngineExecutionException(e,
FrameworkErrorCode.VariablesAssignError, message, stateMachineInstance, state.getName());
EngineUtils.failStateMachine(context, exception);
throw exception;
}
}
context.removeVariable(DomainConstants.VAR_NAME_OUTPUT_PARAMS);
context.removeVariable(DomainConstants.VAR_NAME_INPUT_PARAMS);
if (exp != null && context.getVariable(DomainConstants.VAR_NAME_IS_EXCEPTION_NOT_CATCH) != null
&& (Boolean)context.getVariable(DomainConstants.VAR_NAME_IS_EXCEPTION_NOT_CATCH)) {
//If there is an exception and there is no catch, need to exit the state machine to execute.
context.removeVariable(DomainConstants.VAR_NAME_IS_EXCEPTION_NOT_CATCH);
EngineUtils.failStateMachine(context, exp);
}
}
}

View File

@@ -0,0 +1,433 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.pcext.interceptors;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import io.seata.common.exception.FrameworkErrorCode;
import io.seata.common.loader.LoadLevel;
import io.seata.common.util.CollectionUtils;
import io.seata.saga.engine.StateMachineConfig;
import io.seata.saga.engine.evaluation.Evaluator;
import io.seata.saga.engine.evaluation.EvaluatorFactory;
import io.seata.saga.engine.evaluation.EvaluatorFactoryManager;
import io.seata.saga.engine.evaluation.expression.ExpressionEvaluator;
import io.seata.saga.engine.exception.EngineExecutionException;
import io.seata.saga.engine.pcext.InterceptableStateHandler;
import io.seata.saga.engine.pcext.StateHandlerInterceptor;
import io.seata.saga.engine.pcext.StateInstruction;
import io.seata.saga.engine.pcext.handlers.ServiceTaskStateHandler;
import io.seata.saga.engine.pcext.handlers.SubStateMachineHandler;
import io.seata.saga.engine.pcext.utils.CompensationHolder;
import io.seata.saga.engine.pcext.utils.EngineUtils;
import io.seata.saga.engine.pcext.utils.LoopTaskUtils;
import io.seata.saga.engine.pcext.utils.ParameterUtils;
import io.seata.saga.engine.utils.ExceptionUtils;
import io.seata.saga.proctrl.HierarchicalProcessContext;
import io.seata.saga.proctrl.ProcessContext;
import io.seata.saga.statelang.domain.DomainConstants;
import io.seata.saga.statelang.domain.ExecutionStatus;
import io.seata.saga.statelang.domain.StateInstance;
import io.seata.saga.statelang.domain.StateMachineInstance;
import io.seata.saga.statelang.domain.impl.ServiceTaskStateImpl;
import io.seata.saga.statelang.domain.impl.StateInstanceImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
/**
* StateInterceptor for ServiceTask, SubStateMachine, CompensateState
*
* @author lorne.cl
*/
@LoadLevel(name = "ServiceTask", order = 100)
public class ServiceTaskHandlerInterceptor implements StateHandlerInterceptor {
private static final Logger LOGGER = LoggerFactory.getLogger(ServiceTaskHandlerInterceptor.class);
@Override
public boolean match(Class<? extends InterceptableStateHandler> clazz) {
return clazz != null &&
(ServiceTaskStateHandler.class.isAssignableFrom(clazz)
|| SubStateMachineHandler.class.isAssignableFrom(clazz));
}
@Override
public void preProcess(ProcessContext context) throws EngineExecutionException {
StateInstruction instruction = context.getInstruction(StateInstruction.class);
StateMachineInstance stateMachineInstance = (StateMachineInstance)context.getVariable(
DomainConstants.VAR_NAME_STATEMACHINE_INST);
StateMachineConfig stateMachineConfig = (StateMachineConfig)context.getVariable(
DomainConstants.VAR_NAME_STATEMACHINE_CONFIG);
if (EngineUtils.isTimeout(stateMachineInstance.getGmtUpdated(), stateMachineConfig.getTransOperationTimeout())) {
String message = "Saga Transaction [stateMachineInstanceId:" + stateMachineInstance.getId()
+ "] has timed out, stop execution now.";
LOGGER.error(message);
EngineExecutionException exception = ExceptionUtils.createEngineExecutionException(null,
FrameworkErrorCode.StateMachineExecutionTimeout, message, stateMachineInstance, instruction.getStateName());
EngineUtils.failStateMachine(context, exception);
throw exception;
}
StateInstanceImpl stateInstance = new StateInstanceImpl();
Map<String, Object> contextVariables = (Map<String, Object>)context.getVariable(
DomainConstants.VAR_NAME_STATEMACHINE_CONTEXT);
ServiceTaskStateImpl state = (ServiceTaskStateImpl)instruction.getState(context);
List<Object> serviceInputParams = null;
if (contextVariables != null) {
try {
serviceInputParams = ParameterUtils.createInputParams(stateMachineConfig.getExpressionFactoryManager(), stateInstance,
state, contextVariables);
} catch (Exception e) {
String message = "Task [" + state.getName()
+ "] input parameters assign failed, please check 'Input' expression:" + e.getMessage();
EngineExecutionException exception = ExceptionUtils.createEngineExecutionException(e,
FrameworkErrorCode.VariablesAssignError, message, stateMachineInstance, state.getName());
EngineUtils.failStateMachine(context, exception);
throw exception;
}
}
((HierarchicalProcessContext)context).setVariableLocally(DomainConstants.VAR_NAME_INPUT_PARAMS,
serviceInputParams);
stateInstance.setMachineInstanceId(stateMachineInstance.getId());
stateInstance.setStateMachineInstance(stateMachineInstance);
Object isForCompensation = state.isForCompensation();
if (context.hasVariable(DomainConstants.VAR_NAME_IS_LOOP_STATE) && !Boolean.TRUE.equals(isForCompensation)) {
stateInstance.setName(LoopTaskUtils.generateLoopStateName(context, state.getName()));
StateInstance lastRetriedStateInstance = LoopTaskUtils.findOutLastRetriedStateInstance(stateMachineInstance,
stateInstance.getName());
stateInstance.setStateIdRetriedFor(
lastRetriedStateInstance == null ? null : lastRetriedStateInstance.getId());
} else {
stateInstance.setName(state.getName());
stateInstance.setStateIdRetriedFor(
(String)context.getVariable(state.getName() + DomainConstants.VAR_NAME_RETRIED_STATE_INST_ID));
}
stateInstance.setGmtStarted(new Date());
stateInstance.setGmtUpdated(stateInstance.getGmtStarted());
stateInstance.setStatus(ExecutionStatus.RU);
if (StringUtils.hasLength(stateInstance.getBusinessKey())) {
((Map<String, Object>)context.getVariable(DomainConstants.VAR_NAME_STATEMACHINE_CONTEXT)).put(
state.getName() + DomainConstants.VAR_NAME_BUSINESSKEY, stateInstance.getBusinessKey());
}
stateInstance.setType(state.getType());
stateInstance.setForUpdate(state.isForUpdate());
stateInstance.setServiceName(state.getServiceName());
stateInstance.setServiceMethod(state.getServiceMethod());
stateInstance.setServiceType(state.getServiceType());
if (isForCompensation != null && (Boolean)isForCompensation) {
CompensationHolder compensationHolder = CompensationHolder.getCurrent(context, true);
StateInstance stateToBeCompensated = compensationHolder.getStatesNeedCompensation().get(state.getName());
if (stateToBeCompensated != null) {
stateToBeCompensated.setCompensationState(stateInstance);
stateInstance.setStateIdCompensatedFor(stateToBeCompensated.getId());
} else {
LOGGER.error("Compensation State[{}] has no state to compensate, maybe this is a bug.",
state.getName());
}
CompensationHolder.getCurrent(context, true).addForCompensationState(stateInstance.getName(),
stateInstance);
}
if (DomainConstants.OPERATION_NAME_FORWARD.equals(context.getVariable(DomainConstants.VAR_NAME_OPERATION_NAME))
&& StringUtils.isEmpty(stateInstance.getStateIdRetriedFor()) && !state.isForCompensation()) {
List<StateInstance> stateList = stateMachineInstance.getStateList();
if (CollectionUtils.isNotEmpty(stateList)) {
for (int i = stateList.size() - 1; i >= 0; i--) {
StateInstance executedState = stateList.get(i);
if (stateInstance.getName().equals(executedState.getName())) {
stateInstance.setStateIdRetriedFor(executedState.getId());
executedState.setIgnoreStatus(true);
break;
}
}
}
}
stateInstance.setInputParams(serviceInputParams);
if (stateMachineInstance.getStateMachine().isPersist() && state.isPersist()
&& stateMachineConfig.getStateLogStore() != null) {
try {
stateMachineConfig.getStateLogStore().recordStateStarted(stateInstance, context);
} catch (Exception e) {
String message = "Record state[" + state.getName() + "] started failed, stateMachineInstance[" + stateMachineInstance
.getId() + "], Reason: " + e.getMessage();
EngineExecutionException exception = ExceptionUtils.createEngineExecutionException(e,
FrameworkErrorCode.ExceptionCaught, message, stateMachineInstance, state.getName());
EngineUtils.failStateMachine(context, exception);
throw exception;
}
}
if (StringUtils.isEmpty(stateInstance.getId())) {
stateInstance.setId(stateMachineConfig.getSeqGenerator().generate(DomainConstants.SEQ_ENTITY_STATE_INST));
}
stateMachineInstance.putStateInstance(stateInstance.getId(), stateInstance);
((HierarchicalProcessContext)context).setVariableLocally(DomainConstants.VAR_NAME_STATE_INST, stateInstance);
}
@Override
public void postProcess(ProcessContext context, Exception exp) throws EngineExecutionException {
StateInstruction instruction = context.getInstruction(StateInstruction.class);
ServiceTaskStateImpl state = (ServiceTaskStateImpl)instruction.getState(context);
StateMachineInstance stateMachineInstance = (StateMachineInstance)context.getVariable(
DomainConstants.VAR_NAME_STATEMACHINE_INST);
StateInstance stateInstance = (StateInstance)context.getVariable(DomainConstants.VAR_NAME_STATE_INST);
if (stateInstance == null || !stateMachineInstance.isRunning()) {
LOGGER.warn("StateMachineInstance[id:" + stateMachineInstance.getId() + "] is end. stop running");
return;
}
StateMachineConfig stateMachineConfig = (StateMachineConfig)context.getVariable(
DomainConstants.VAR_NAME_STATEMACHINE_CONFIG);
if (exp == null) {
exp = (Exception)context.getVariable(DomainConstants.VAR_NAME_CURRENT_EXCEPTION);
}
stateInstance.setException(exp);
decideExecutionStatus(context, stateInstance, state, exp);
if (ExecutionStatus.SU.equals(stateInstance.getStatus()) && exp != null) {
if (LOGGER.isInfoEnabled()) {
LOGGER.info(
"Although an exception occurs, the execution status map to SU, and the exception is ignored when "
+ "the execution status decision.");
}
context.removeVariable(DomainConstants.VAR_NAME_CURRENT_EXCEPTION);
}
Map<String, Object> contextVariables = (Map<String, Object>)context.getVariable(
DomainConstants.VAR_NAME_STATEMACHINE_CONTEXT);
Object serviceOutputParams = context.getVariable(DomainConstants.VAR_NAME_OUTPUT_PARAMS);
if (serviceOutputParams != null) {
try {
Map<String, Object> outputVariablesToContext = ParameterUtils.createOutputParams(
stateMachineConfig.getExpressionFactoryManager(), state, serviceOutputParams);
if (CollectionUtils.isNotEmpty(outputVariablesToContext)) {
contextVariables.putAll(outputVariablesToContext);
}
} catch (Exception e) {
String message = "Task [" + state.getName()
+ "] output parameters assign failed, please check 'Output' expression:" + e.getMessage();
EngineExecutionException exception = ExceptionUtils.createEngineExecutionException(e,
FrameworkErrorCode.VariablesAssignError, message, stateMachineInstance, stateInstance);
if (stateMachineInstance.getStateMachine().isPersist() && state.isPersist()
&& stateMachineConfig.getStateLogStore() != null) {
stateMachineConfig.getStateLogStore().recordStateFinished(stateInstance, context);
}
EngineUtils.failStateMachine(context, exception);
throw exception;
}
}
context.removeVariable(DomainConstants.VAR_NAME_OUTPUT_PARAMS);
context.removeVariable(DomainConstants.VAR_NAME_INPUT_PARAMS);
stateInstance.setGmtEnd(new Date());
if (stateMachineInstance.getStateMachine().isPersist() && state.isPersist()
&& stateMachineConfig.getStateLogStore() != null) {
stateMachineConfig.getStateLogStore().recordStateFinished(stateInstance, context);
}
if (exp != null && context.getVariable(DomainConstants.VAR_NAME_IS_EXCEPTION_NOT_CATCH) != null
&& (Boolean)context.getVariable(DomainConstants.VAR_NAME_IS_EXCEPTION_NOT_CATCH)) {
//If there is an exception and there is no catch, need to exit the state machine to execute.
context.removeVariable(DomainConstants.VAR_NAME_IS_EXCEPTION_NOT_CATCH);
EngineUtils.failStateMachine(context, exp);
}
}
private void decideExecutionStatus(ProcessContext context, StateInstance stateInstance, ServiceTaskStateImpl state,
Exception exp) {
Map<String, String> statusMatchList = state.getStatus();
if (CollectionUtils.isNotEmpty(statusMatchList)) {
if (state.isAsync()) {
if (LOGGER.isWarnEnabled()) {
LOGGER.warn(
"Service[{}.{}] is execute asynchronously, null return value collected, so user defined "
+ "Status Matching skipped. stateName: {}, branchId: {}", state.getServiceName(),
state.getServiceMethod(), state.getName(), stateInstance.getId());
}
} else {
StateMachineConfig stateMachineConfig = (StateMachineConfig)context.getVariable(
DomainConstants.VAR_NAME_STATEMACHINE_CONFIG);
Map<Object, String> statusEvaluators = state.getStatusEvaluators();
if (statusEvaluators == null) {
synchronized (state) {
statusEvaluators = state.getStatusEvaluators();
if (statusEvaluators == null) {
statusEvaluators = new LinkedHashMap<>(statusMatchList.size());
String expressionStr, statusVal;
Evaluator evaluator;
for (Map.Entry<String, String> entry : statusMatchList.entrySet()) {
expressionStr = entry.getKey();
statusVal = entry.getValue();
evaluator = createEvaluator(stateMachineConfig.getEvaluatorFactoryManager(), expressionStr);
if (evaluator != null) {
statusEvaluators.put(evaluator, statusVal);
}
}
}
state.setStatusEvaluators(statusEvaluators);
}
}
for (Object evaluatorObj : statusEvaluators.keySet()) {
Evaluator evaluator = (Evaluator)evaluatorObj;
String statusVal = statusEvaluators.get(evaluator);
if (evaluator.evaluate(context.getVariables())) {
stateInstance.setStatus(ExecutionStatus.valueOf(statusVal));
break;
}
}
if (exp == null && (stateInstance.getStatus() == null || ExecutionStatus.RU.equals(
stateInstance.getStatus()))) {
if (state.isForUpdate()) {
stateInstance.setStatus(ExecutionStatus.UN);
} else {
stateInstance.setStatus(ExecutionStatus.FA);
}
stateInstance.setGmtEnd(new Date());
StateMachineInstance stateMachineInstance = stateInstance.getStateMachineInstance();
if (stateMachineInstance.getStateMachine().isPersist() && state.isPersist()
&& stateMachineConfig.getStateLogStore() != null) {
stateMachineConfig.getStateLogStore().recordStateFinished(stateInstance, context);
}
EngineExecutionException exception = new EngineExecutionException("State [" + state.getName()
+ "] execute finished, but cannot matching status, pls check its status manually",
FrameworkErrorCode.NoMatchedStatus);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("State[{}] execute finish with status[{}]", state.getName(),
stateInstance.getStatus());
}
EngineUtils.failStateMachine(context, exception);
throw exception;
}
}
}
if (stateInstance.getStatus() == null || ExecutionStatus.RU.equals(stateInstance.getStatus())) {
if (exp == null) {
stateInstance.setStatus(ExecutionStatus.SU);
} else {
if (state.isForUpdate() || state.isForCompensation()) {
stateInstance.setStatus(ExecutionStatus.UN);
ExceptionUtils.NetExceptionType t = ExceptionUtils.getNetExceptionType(exp);
if (t != null) {
if (t.equals(ExceptionUtils.NetExceptionType.CONNECT_EXCEPTION)) {
stateInstance.setStatus(ExecutionStatus.FA);
} else if (t.equals(ExceptionUtils.NetExceptionType.READ_TIMEOUT_EXCEPTION)) {
stateInstance.setStatus(ExecutionStatus.UN);
}
} else {
stateInstance.setStatus(ExecutionStatus.UN);
}
} else {
stateInstance.setStatus(ExecutionStatus.FA);
}
}
}
if (LOGGER.isInfoEnabled()) {
LOGGER.info("State[{}] finish with status[{}]", state.getName(), stateInstance.getStatus());
}
}
private Evaluator createEvaluator(EvaluatorFactoryManager evaluatorFactoryManager, String expressionStr) {
String expressionType = null;
String expressionContent = null;
Evaluator evaluator = null;
if (StringUtils.hasLength(expressionStr)) {
if (expressionStr.startsWith("$")) {
int expTypeStart = expressionStr.indexOf("$");
int expTypeEnd = expressionStr.indexOf("{", expTypeStart);
if (expTypeStart >= 0 && expTypeEnd > expTypeStart) {
expressionType = expressionStr.substring(expTypeStart + 1, expTypeEnd);
}
int expEnd = expressionStr.lastIndexOf("}");
if (expTypeEnd > 0 && expEnd > expTypeEnd) {
expressionContent = expressionStr.substring(expTypeEnd + 1, expEnd);
}
} else {
expressionContent = expressionStr;
}
EvaluatorFactory evaluatorFactory = evaluatorFactoryManager.getEvaluatorFactory(expressionType);
if (evaluatorFactory == null) {
throw new IllegalArgumentException("Cannot get EvaluatorFactory by Type[" + expressionType + "]");
}
evaluator = evaluatorFactory.createEvaluator(expressionContent);
if (evaluator instanceof ExpressionEvaluator) {
((ExpressionEvaluator)evaluator).setRootObjectName(DomainConstants.VAR_NAME_OUTPUT_PARAMS);
}
}
return evaluator;
}
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.pcext.routers;
import java.util.ArrayList;
import java.util.List;
import io.seata.saga.engine.exception.EngineExecutionException;
import io.seata.saga.engine.pcext.InterceptableStateRouter;
import io.seata.saga.engine.pcext.StateRouter;
import io.seata.saga.engine.pcext.StateRouterInterceptor;
import io.seata.saga.proctrl.Instruction;
import io.seata.saga.proctrl.ProcessContext;
import io.seata.saga.statelang.domain.State;
/**
* EndState Router
*
* @author lorne.cl
*/
public class EndStateRouter implements StateRouter, InterceptableStateRouter {
private List<StateRouterInterceptor> interceptors = new ArrayList<>();
@Override
public Instruction route(ProcessContext context, State state) throws EngineExecutionException {
return null;//Return null to stop execution
}
@Override
public List<StateRouterInterceptor> getInterceptors() {
return interceptors;
}
@Override
public void addInterceptor(StateRouterInterceptor interceptor) {
if (interceptors != null && !interceptors.contains(interceptor)) {
interceptors.add(interceptor);
}
}
public void setInterceptors(List<StateRouterInterceptor> interceptors) {
this.interceptors = interceptors;
}
}

View File

@@ -0,0 +1,195 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.pcext.routers;
import java.util.Stack;
import io.seata.common.exception.FrameworkErrorCode;
import io.seata.saga.engine.exception.EngineExecutionException;
import io.seata.saga.engine.pcext.StateInstruction;
import io.seata.saga.engine.pcext.StateRouter;
import io.seata.saga.engine.pcext.utils.CompensationHolder;
import io.seata.saga.engine.pcext.utils.EngineUtils;
import io.seata.saga.engine.pcext.utils.LoopTaskUtils;
import io.seata.saga.proctrl.HierarchicalProcessContext;
import io.seata.saga.proctrl.Instruction;
import io.seata.saga.proctrl.ProcessContext;
import io.seata.saga.statelang.domain.CompensateSubStateMachineState;
import io.seata.saga.statelang.domain.DomainConstants;
import io.seata.saga.statelang.domain.ExecutionStatus;
import io.seata.saga.statelang.domain.State;
import io.seata.saga.statelang.domain.StateInstance;
import io.seata.saga.statelang.domain.StateMachine;
import io.seata.saga.statelang.domain.SubStateMachine;
import io.seata.saga.statelang.domain.impl.AbstractTaskState;
import io.seata.saga.statelang.domain.impl.LoopStartStateImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
/**
* TaskState Router
*
* @author lorne.cl
*/
public class TaskStateRouter implements StateRouter {
private static final Logger LOGGER = LoggerFactory.getLogger(TaskStateRouter.class);
@Override
public Instruction route(ProcessContext context, State state) throws EngineExecutionException {
StateInstruction stateInstruction = context.getInstruction(StateInstruction.class);
if (stateInstruction.isEnd()) {
if (LOGGER.isInfoEnabled()) {
LOGGER.info(
"StateInstruction is ended, Stop the StateMachine executing. StateMachine[{}] Current State[{}]",
stateInstruction.getStateMachineName(), state.getName());
}
return null;
}
// check if in loop async condition
if (Boolean.TRUE.equals(context.getVariable(DomainConstants.VAR_NAME_IS_LOOP_STATE))) {
return null;
}
//The current CompensationTriggerState can mark the compensation process is started and perform compensation
// route processing.
State compensationTriggerState = (State)context.getVariable(
DomainConstants.VAR_NAME_CURRENT_COMPEN_TRIGGER_STATE);
if (compensationTriggerState != null) {
return compensateRoute(context, compensationTriggerState);
}
//There is an exception route, indicating that an exception is thrown, and the exception route is prioritized.
String next = (String)context.getVariable(DomainConstants.VAR_NAME_CURRENT_EXCEPTION_ROUTE);
if (StringUtils.hasLength(next)) {
context.removeVariable(DomainConstants.VAR_NAME_CURRENT_EXCEPTION_ROUTE);
} else {
next = state.getNext();
}
//If next is empty, the state selected by the Choice state was taken.
if (!StringUtils.hasLength(next) && context.hasVariable(DomainConstants.VAR_NAME_CURRENT_CHOICE)) {
next = (String)context.getVariable(DomainConstants.VAR_NAME_CURRENT_CHOICE);
context.removeVariable(DomainConstants.VAR_NAME_CURRENT_CHOICE);
}
if (!StringUtils.hasLength(next)) {
return null;
}
StateMachine stateMachine = state.getStateMachine();
State nextState = stateMachine.getState(next);
if (nextState == null) {
throw new EngineExecutionException("Next state[" + next + "] is not exits",
FrameworkErrorCode.ObjectNotExists);
}
stateInstruction.setStateName(next);
if (null != LoopTaskUtils.getLoopConfig(context, nextState)) {
stateInstruction.setTemporaryState(new LoopStartStateImpl());
}
return stateInstruction;
}
private Instruction compensateRoute(ProcessContext context, State compensationTriggerState) {
//If there is already a compensation state that has been executed,
// it is judged whether it is wrong or unsuccessful,
// and the compensation process is interrupted.
if (Boolean.TRUE.equals(context.getVariable(DomainConstants.VAR_NAME_FIRST_COMPENSATION_STATE_STARTED))) {
Exception exception = (Exception)context.getVariable(DomainConstants.VAR_NAME_CURRENT_EXCEPTION);
if (exception != null) {
EngineUtils.endStateMachine(context);
return null;
}
StateInstance stateInstance = (StateInstance)context.getVariable(DomainConstants.VAR_NAME_STATE_INST);
if (stateInstance != null && (!ExecutionStatus.SU.equals(stateInstance.getStatus()))) {
EngineUtils.endStateMachine(context);
return null;
}
}
Stack<StateInstance> stateStackToBeCompensated = CompensationHolder.getCurrent(context, true)
.getStateStackNeedCompensation();
if (!stateStackToBeCompensated.isEmpty()) {
StateInstance stateToBeCompensated = stateStackToBeCompensated.pop();
StateMachine stateMachine = (StateMachine)context.getVariable(DomainConstants.VAR_NAME_STATEMACHINE);
State state = stateMachine.getState(EngineUtils.getOriginStateName(stateToBeCompensated));
if (state != null && state instanceof AbstractTaskState) {
AbstractTaskState taskState = (AbstractTaskState)state;
StateInstruction instruction = context.getInstruction(StateInstruction.class);
State compensateState = null;
String compensateStateName = taskState.getCompensateState();
if (StringUtils.hasLength(compensateStateName)) {
compensateState = stateMachine.getState(compensateStateName);
}
if (compensateState == null && (taskState instanceof SubStateMachine)) {
compensateState = ((SubStateMachine)taskState).getCompensateStateObject();
instruction.setTemporaryState(compensateState);
}
if (compensateState == null) {
EngineUtils.endStateMachine(context);
return null;
}
instruction.setStateName(compensateState.getName());
CompensationHolder.getCurrent(context, true).addToBeCompensatedState(compensateState.getName(),
stateToBeCompensated);
((HierarchicalProcessContext)context).setVariableLocally(
DomainConstants.VAR_NAME_FIRST_COMPENSATION_STATE_STARTED, true);
if (compensateState instanceof CompensateSubStateMachineState) {
((HierarchicalProcessContext)context).setVariableLocally(
compensateState.getName() + DomainConstants.VAR_NAME_SUB_MACHINE_PARENT_ID,
EngineUtils.generateParentId(stateToBeCompensated));
}
return instruction;
}
}
context.removeVariable(DomainConstants.VAR_NAME_CURRENT_COMPEN_TRIGGER_STATE);
String compensationTriggerStateNext = compensationTriggerState.getNext();
if (StringUtils.isEmpty(compensationTriggerStateNext)) {
EngineUtils.endStateMachine(context);
return null;
}
StateInstruction instruction = context.getInstruction(StateInstruction.class);
instruction.setStateName(compensationTriggerStateNext);
return instruction;
}
}

View File

@@ -0,0 +1,163 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.pcext.utils;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import java.util.concurrent.ConcurrentHashMap;
import io.seata.common.exception.FrameworkErrorCode;
import io.seata.common.util.CollectionUtils;
import io.seata.common.util.StringUtils;
import io.seata.saga.engine.exception.EngineExecutionException;
import io.seata.saga.engine.utils.ExceptionUtils;
import io.seata.saga.proctrl.ProcessContext;
import io.seata.saga.statelang.domain.DomainConstants;
import io.seata.saga.statelang.domain.ExecutionStatus;
import io.seata.saga.statelang.domain.State;
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.AbstractTaskState;
/**
* CompensationHolder
*
* @author lorne.cl
*/
public class CompensationHolder {
/**
* states need compensation
* key: stateName
*/
private Map<String, StateInstance> statesNeedCompensation = new ConcurrentHashMap<>();
/**
* states used to compensation
* key: stateName
*/
private Map<String, StateInstance> statesForCompensation = new ConcurrentHashMap<>();
/**
* stateStack need compensation
*/
private Stack<StateInstance> stateStackNeedCompensation = new Stack<>();
public static CompensationHolder getCurrent(ProcessContext context, boolean forceCreate) {
CompensationHolder compensationholder = (CompensationHolder)context.getVariable(
DomainConstants.VAR_NAME_CURRENT_COMPENSATION_HOLDER);
if (compensationholder == null && forceCreate) {
synchronized (context) {
compensationholder = (CompensationHolder)context.getVariable(
DomainConstants.VAR_NAME_CURRENT_COMPENSATION_HOLDER);
if (compensationholder == null) {
compensationholder = new CompensationHolder();
context.setVariable(DomainConstants.VAR_NAME_CURRENT_COMPENSATION_HOLDER, compensationholder);
}
}
}
return compensationholder;
}
public static List<StateInstance> findStateInstListToBeCompensated(ProcessContext context,
List<StateInstance> stateInstanceList) {
List<StateInstance> stateListToBeCompensated = null;
if (CollectionUtils.isNotEmpty(stateInstanceList)) {
stateListToBeCompensated = new ArrayList<>(stateInstanceList.size());
StateMachine stateMachine = (StateMachine)context.getVariable(DomainConstants.VAR_NAME_STATEMACHINE);
StateMachineInstance stateMachineInstance = (StateMachineInstance)context.getVariable(
DomainConstants.VAR_NAME_STATEMACHINE_INST);
for (StateInstance stateInstance : stateInstanceList) {
if (stateNeedToCompensate(stateInstance)) {
State state = stateMachine.getState(EngineUtils.getOriginStateName(stateInstance));
AbstractTaskState taskState = null;
if (state instanceof AbstractTaskState) {
taskState = (AbstractTaskState)state;
}
//The data update service is not configured with the compensation state,
// The state machine needs to exit directly without compensation.
if (stateInstance.isForUpdate() && taskState != null && StringUtils.isBlank(
taskState.getCompensateState())) {
String message = "StateMachineInstance[" + stateMachineInstance.getId() + ":" + stateMachine
.getName() + "] have a state [" + stateInstance.getName()
+ "] is a service for update data, but no compensateState found.";
EngineExecutionException exception = ExceptionUtils.createEngineExecutionException(
FrameworkErrorCode.CompensationStateNotFound, message, stateMachineInstance, stateInstance);
EngineUtils.failStateMachine(context, exception);
throw exception;
}
if (taskState != null && StringUtils.isNotBlank(taskState.getCompensateState())) {
stateListToBeCompensated.add(stateInstance);
}
}
}
}
return stateListToBeCompensated;
}
private static boolean stateNeedToCompensate(StateInstance stateInstance) {
//If it has been retried, it will not be compensated
if (stateInstance.isIgnoreStatus()) {
return false;
}
if (DomainConstants.STATE_TYPE_SUB_STATE_MACHINE.equals(stateInstance.getType())) {
return (!ExecutionStatus.FA.equals(stateInstance.getStatus())) && (!ExecutionStatus.SU.equals(
stateInstance.getCompensationStatus()));
} else {
return DomainConstants.STATE_TYPE_SERVICE_TASK.equals(stateInstance.getType()) && !stateInstance
.isForCompensation() && (!ExecutionStatus.FA.equals(stateInstance.getStatus())) && (!ExecutionStatus.SU
.equals(stateInstance.getCompensationStatus()));
}
}
public static void clearCurrent(ProcessContext context) {
context.removeVariable(DomainConstants.VAR_NAME_CURRENT_COMPENSATION_HOLDER);
}
public Map<String, StateInstance> getStatesNeedCompensation() {
return statesNeedCompensation;
}
public void addToBeCompensatedState(String stateName, StateInstance toBeCompensatedState) {
this.statesNeedCompensation.put(stateName, toBeCompensatedState);
}
public Map<String, StateInstance> getStatesForCompensation() {
return statesForCompensation;
}
public void addForCompensationState(String stateName, StateInstance forCompensationState) {
this.statesForCompensation.put(stateName, forCompensationState);
}
public Stack<StateInstance> getStateStackNeedCompensation() {
return stateStackNeedCompensation;
}
}

View File

@@ -0,0 +1,251 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.pcext.utils;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Semaphore;
import io.seata.common.util.CollectionUtils;
import io.seata.common.util.StringUtils;
import io.seata.saga.engine.AsyncCallback;
import io.seata.saga.engine.StateMachineConfig;
import io.seata.saga.engine.pcext.StateInstruction;
import io.seata.saga.engine.pcext.handlers.ScriptTaskStateHandler;
import io.seata.saga.proctrl.HierarchicalProcessContext;
import io.seata.saga.proctrl.ProcessContext;
import io.seata.saga.statelang.domain.DomainConstants;
import io.seata.saga.statelang.domain.StateInstance;
import io.seata.saga.statelang.domain.StateMachineInstance;
import io.seata.saga.statelang.domain.TaskState;
import io.seata.saga.statelang.domain.TaskState.ExceptionMatch;
import io.seata.saga.statelang.domain.impl.AbstractTaskState;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author lorne.cl
*/
public class EngineUtils {
private static final Logger LOGGER = LoggerFactory.getLogger(EngineUtils.class);
/**
* generate parent id
*
* @param stateInstance
* @return
*/
public static String generateParentId(StateInstance stateInstance) {
return stateInstance.getMachineInstanceId() + DomainConstants.SEPERATOR_PARENT_ID + stateInstance.getId();
}
/**
* get origin state name without suffix like fork
*
* @param stateInstance
* @return
* @see LoopTaskUtils#generateLoopStateName(ProcessContext, String)
*/
public static String getOriginStateName(StateInstance stateInstance) {
String stateName = stateInstance.getName();
if (StringUtils.isNotBlank(stateName)) {
int end = stateName.lastIndexOf(LoopTaskUtils.LOOP_STATE_NAME_PATTERN);
if (end > -1) {
return stateName.substring(0, end);
}
}
return stateName;
}
/**
* end StateMachine
*
* @param context
*/
public static void endStateMachine(ProcessContext context) {
if (context.hasVariable(DomainConstants.VAR_NAME_IS_LOOP_STATE)) {
if (context.hasVariable(DomainConstants.LOOP_SEMAPHORE)) {
Semaphore semaphore = (Semaphore)context.getVariable(DomainConstants.LOOP_SEMAPHORE);
semaphore.release();
}
return;
}
StateMachineInstance stateMachineInstance = (StateMachineInstance)context.getVariable(
DomainConstants.VAR_NAME_STATEMACHINE_INST);
stateMachineInstance.setGmtEnd(new Date());
Exception exp = (Exception)context.getVariable(DomainConstants.VAR_NAME_CURRENT_EXCEPTION);
if (exp != null) {
stateMachineInstance.setException(exp);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Exception Occurred: " + exp);
}
}
StateMachineConfig stateMachineConfig = (StateMachineConfig)context.getVariable(
DomainConstants.VAR_NAME_STATEMACHINE_CONFIG);
stateMachineConfig.getStatusDecisionStrategy().decideOnEndState(context, stateMachineInstance, exp);
stateMachineInstance.getEndParams().putAll(
(Map<String, Object>)context.getVariable(DomainConstants.VAR_NAME_STATEMACHINE_CONTEXT));
StateInstruction instruction = context.getInstruction(StateInstruction.class);
instruction.setEnd(true);
stateMachineInstance.setRunning(false);
stateMachineInstance.setGmtEnd(new Date());
if (stateMachineInstance.getStateMachine().isPersist() && stateMachineConfig.getStateLogStore() != null) {
stateMachineConfig.getStateLogStore().recordStateMachineFinished(stateMachineInstance, context);
}
AsyncCallback callback = (AsyncCallback)context.getVariable(DomainConstants.VAR_NAME_ASYNC_CALLBACK);
if (callback != null) {
if (exp != null) {
callback.onError(context, stateMachineInstance, exp);
} else {
callback.onFinished(context, stateMachineInstance);
}
}
}
/**
* fail StateMachine
*
* @param context
* @param exp
*/
public static void failStateMachine(ProcessContext context, Exception exp) {
if (context.hasVariable(DomainConstants.VAR_NAME_IS_LOOP_STATE)) {
return;
}
StateMachineInstance stateMachineInstance = (StateMachineInstance)context.getVariable(
DomainConstants.VAR_NAME_STATEMACHINE_INST);
StateMachineConfig stateMachineConfig = (StateMachineConfig)context.getVariable(
DomainConstants.VAR_NAME_STATEMACHINE_CONFIG);
stateMachineConfig.getStatusDecisionStrategy().decideOnTaskStateFail(context, stateMachineInstance, exp);
stateMachineInstance.getEndParams().putAll(
(Map<String, Object>)context.getVariable(DomainConstants.VAR_NAME_STATEMACHINE_CONTEXT));
StateInstruction instruction = context.getInstruction(StateInstruction.class);
instruction.setEnd(true);
stateMachineInstance.setRunning(false);
stateMachineInstance.setGmtEnd(new Date());
stateMachineInstance.setException(exp);
if (stateMachineInstance.getStateMachine().isPersist() && stateMachineConfig.getStateLogStore() != null) {
stateMachineConfig.getStateLogStore().recordStateMachineFinished(stateMachineInstance, context);
}
AsyncCallback callback = (AsyncCallback)context.getVariable(DomainConstants.VAR_NAME_ASYNC_CALLBACK);
if (callback != null) {
callback.onError(context, stateMachineInstance, exp);
}
}
/**
* test if is timeout
* @param gmtUpdated
* @param timeoutMillis
* @return
*/
public static boolean isTimeout(Date gmtUpdated, int timeoutMillis) {
if (gmtUpdated == null || timeoutMillis < 0) {
return false;
}
return System.currentTimeMillis() - gmtUpdated.getTime() > timeoutMillis;
}
/**
* Handle exceptions while ServiceTask or ScriptTask Executing
*
* @param context
* @param state
* @param e
*/
public static void handleException(ProcessContext context, AbstractTaskState state, Throwable e) {
List<ExceptionMatch> catches = state.getCatches();
if (CollectionUtils.isNotEmpty(catches)) {
for (TaskState.ExceptionMatch exceptionMatch : catches) {
List<String> exceptions = exceptionMatch.getExceptions();
List<Class<? extends Exception>> exceptionClasses = exceptionMatch.getExceptionClasses();
if (CollectionUtils.isNotEmpty(exceptions)) {
if (exceptionClasses == null) {
synchronized (exceptionMatch) {
exceptionClasses = exceptionMatch.getExceptionClasses();
if (exceptionClasses == null) {
exceptionClasses = new ArrayList<>(exceptions.size());
for (String expStr : exceptions) {
Class<? extends Exception> expClass = null;
try {
expClass = (Class<? extends Exception>) ScriptTaskStateHandler.class
.getClassLoader().loadClass(expStr);
} catch (Exception e1) {
LOGGER.warn("Cannot Load Exception Class by getClass().getClassLoader()", e1);
try {
expClass = (Class<? extends Exception>) Thread.currentThread()
.getContextClassLoader().loadClass(expStr);
} catch (Exception e2) {
LOGGER.warn(
"Cannot Load Exception Class by Thread.currentThread()"
+ ".getContextClassLoader()",
e2);
}
}
if (expClass != null) {
exceptionClasses.add(expClass);
}
}
exceptionMatch.setExceptionClasses(exceptionClasses);
}
}
}
for (Class<? extends Exception> expClass : exceptionClasses) {
if (expClass.isAssignableFrom(e.getClass())) {
((HierarchicalProcessContext) context).setVariableLocally(
DomainConstants.VAR_NAME_CURRENT_EXCEPTION_ROUTE, exceptionMatch.getNext());
return;
}
}
}
}
}
LOGGER.error("Task execution failed and no catches configured");
((HierarchicalProcessContext) context).setVariableLocally(DomainConstants.VAR_NAME_IS_EXCEPTION_NOT_CATCH, true);
}
}

View File

@@ -0,0 +1,105 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.pcext.utils;
import java.util.Collection;
import java.util.Stack;
import java.util.concurrent.atomic.AtomicInteger;
import io.seata.saga.proctrl.ProcessContext;
import io.seata.saga.statelang.domain.DomainConstants;
/**
* Loop Context Holder for Loop Attributes
*
* @author anselleeyy
*/
public class LoopContextHolder {
private final AtomicInteger nrOfInstances = new AtomicInteger();
private final AtomicInteger nrOfActiveInstances = new AtomicInteger();
private final AtomicInteger nrOfCompletedInstances = new AtomicInteger();
private volatile boolean failEnd = false;
private volatile boolean completionConditionSatisfied = false;
private final Stack<Integer> loopCounterStack = new Stack<>();
private final Stack<Integer> forwardCounterStack = new Stack<>();
private Collection collection;
public static LoopContextHolder getCurrent(ProcessContext context, boolean forceCreate) {
LoopContextHolder loopContextHolder = (LoopContextHolder)context.getVariable(
DomainConstants.VAR_NAME_CURRENT_LOOP_CONTEXT_HOLDER);
if (null == loopContextHolder && forceCreate) {
synchronized (context) {
loopContextHolder = (LoopContextHolder)context.getVariable(
DomainConstants.VAR_NAME_CURRENT_LOOP_CONTEXT_HOLDER);
if (null == loopContextHolder) {
loopContextHolder = new LoopContextHolder();
context.setVariable(DomainConstants.VAR_NAME_CURRENT_LOOP_CONTEXT_HOLDER, loopContextHolder);
}
}
}
return loopContextHolder;
}
public static void clearCurrent(ProcessContext context) {
context.removeVariable(DomainConstants.VAR_NAME_CURRENT_LOOP_CONTEXT_HOLDER);
}
public AtomicInteger getNrOfInstances() {
return nrOfInstances;
}
public AtomicInteger getNrOfActiveInstances() {
return nrOfActiveInstances;
}
public AtomicInteger getNrOfCompletedInstances() {
return nrOfCompletedInstances;
}
public boolean isFailEnd() {
return failEnd;
}
public void setFailEnd(boolean failEnd) {
this.failEnd = failEnd;
}
public boolean isCompletionConditionSatisfied() {
return completionConditionSatisfied;
}
public void setCompletionConditionSatisfied(boolean completionConditionSatisfied) {
this.completionConditionSatisfied = completionConditionSatisfied;
}
public Stack<Integer> getLoopCounterStack() {
return loopCounterStack;
}
public Stack<Integer> getForwardCounterStack() {
return forwardCounterStack;
}
public Collection getCollection() {
return collection;
}
public void setCollection(Collection collection) {
this.collection = collection;
}
}

View File

@@ -0,0 +1,429 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.pcext.utils;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EmptyStackException;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import io.seata.common.exception.FrameworkErrorCode;
import io.seata.common.util.CollectionUtils;
import io.seata.common.util.NumberUtils;
import io.seata.common.util.StringUtils;
import io.seata.saga.engine.StateMachineConfig;
import io.seata.saga.engine.evaluation.EvaluatorFactoryManager;
import io.seata.saga.engine.evaluation.expression.ExpressionEvaluator;
import io.seata.saga.engine.exception.ForwardInvalidException;
import io.seata.saga.engine.pcext.StateInstruction;
import io.seata.saga.proctrl.ProcessContext;
import io.seata.saga.proctrl.impl.ProcessContextImpl;
import io.seata.saga.statelang.domain.DomainConstants;
import io.seata.saga.statelang.domain.ExecutionStatus;
import io.seata.saga.statelang.domain.State;
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.TaskState.Loop;
import io.seata.saga.statelang.domain.impl.AbstractTaskState;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Loop Task Util
*
* @author anselleeyy
*/
public class LoopTaskUtils {
private static final Logger LOGGER = LoggerFactory.getLogger(LoopTaskUtils.class);
private static final String DEFAULT_COMPLETION_CONDITION = "[nrOfInstances] == [nrOfCompletedInstances]";
public static final String LOOP_STATE_NAME_PATTERN = "-loop-";
private static final Map<String, ExpressionEvaluator> EXPRESSION_EVALUATOR_MAP = new ConcurrentHashMap<>();
/**
* get Loop Config from State
*
* @param context
* @param currentState
* @return currentState loop config if satisfied, else {@literal null}
*/
public static Loop getLoopConfig(ProcessContext context, State currentState) {
if (matchLoop(currentState)) {
AbstractTaskState taskState = (AbstractTaskState)currentState;
StateMachineInstance stateMachineInstance = (StateMachineInstance)context.getVariable(
DomainConstants.VAR_NAME_STATEMACHINE_INST);
StateMachineConfig stateMachineConfig = (StateMachineConfig)context.getVariable(
DomainConstants.VAR_NAME_STATEMACHINE_CONFIG);
if (null != taskState.getLoop()) {
Loop loop = taskState.getLoop();
String collectionName = loop.getCollection();
if (StringUtils.isNotBlank(collectionName)) {
Object expression = ParameterUtils.createValueExpression(
stateMachineConfig.getExpressionFactoryManager(), collectionName);
Object collection = ParameterUtils.getValue(expression, stateMachineInstance.getContext(), null);
if (collection instanceof Collection && ((Collection)collection).size() > 0) {
LoopContextHolder.getCurrent(context, true).setCollection((Collection)collection);
return loop;
}
}
LOGGER.warn("State [{}] loop collection param [{}] invalid", currentState.getName(), collectionName);
}
}
return null;
}
/**
* match if state has loop property
*
* @param state
* @return
*/
public static boolean matchLoop(State state) {
return state != null && (DomainConstants.STATE_TYPE_SERVICE_TASK.equals(state.getType())
|| DomainConstants.STATE_TYPE_SCRIPT_TASK.equals(state.getType())
|| DomainConstants.STATE_TYPE_SUB_STATE_MACHINE.equals(state.getType()));
}
/**
* create loop counter context
*
* @param context
*/
public static void createLoopCounterContext(ProcessContext context) {
LoopContextHolder contextHolder = LoopContextHolder.getCurrent(context, true);
Collection collection = contextHolder.getCollection();
contextHolder.getNrOfInstances().set(collection.size());
for (int i = collection.size() - 1; i >= 0; i--) {
contextHolder.getLoopCounterStack().push(i);
}
}
/**
* reload loop counter context while forward
*
* @param context
* @param forwardStateName
*/
public static void reloadLoopContext(ProcessContext context, String forwardStateName) {
StateMachineInstance stateMachineInstance = (StateMachineInstance)context.getVariable(
DomainConstants.VAR_NAME_STATEMACHINE_INST);
List<StateInstance> actList = stateMachineInstance.getStateList();
List<StateInstance> forwardStateList = actList.stream().filter(
e -> forwardStateName.equals(EngineUtils.getOriginStateName(e))).collect(Collectors.toList());
LoopContextHolder loopContextHolder = LoopContextHolder.getCurrent(context, true);
Collection collection = loopContextHolder.getCollection();
LinkedList<Integer> list = new LinkedList<>();
for (int i = 0; i < collection.size(); i++) {
list.addFirst(i);
}
int executedNumber = 0;
LinkedList<Integer> failEndList = new LinkedList<>();
for (StateInstance stateInstance : forwardStateList) {
if (!stateInstance.isIgnoreStatus()) {
if (ExecutionStatus.SU.equals(stateInstance.getStatus())) {
executedNumber += 1;
} else {
stateInstance.setIgnoreStatus(true);
failEndList.addFirst(reloadLoopCounter(stateInstance.getName()));
}
list.remove(Integer.valueOf(reloadLoopCounter(stateInstance.getName())));
}
}
loopContextHolder.getLoopCounterStack().addAll(list);
loopContextHolder.getForwardCounterStack().addAll(failEndList);
loopContextHolder.getNrOfInstances().set(collection.size());
loopContextHolder.getNrOfCompletedInstances().set(executedNumber);
}
/**
* create context for async publish
*
* @param context
* @param loopCounter acquire new counter if is -1, else means a specific loop-counter
* @return
*/
public static ProcessContext createLoopEventContext(ProcessContext context, int loopCounter) {
ProcessContextImpl copyContext = new ProcessContextImpl();
copyContext.setParent(context);
copyContext.setVariableLocally(DomainConstants.LOOP_COUNTER, loopCounter >= 0 ? loopCounter : acquireNextLoopCounter(context));
copyContext.setInstruction(copyInstruction(context.getInstruction(StateInstruction.class)));
return copyContext;
}
public static StateInstance findOutLastNeedForwardStateInstance(ProcessContext context) {
StateMachineInstance stateMachineInstance = (StateMachineInstance)context.getVariable(
DomainConstants.VAR_NAME_STATEMACHINE_INST);
StateInstance lastForwardState = (StateInstance)context.getVariable(DomainConstants.VAR_NAME_STATE_INST);
List<StateInstance> actList = stateMachineInstance.getStateList();
for (int i = actList.size() - 1; i >= 0; i--) {
StateInstance stateInstance = actList.get(i);
if (EngineUtils.getOriginStateName(stateInstance).equals(EngineUtils.getOriginStateName(lastForwardState))
&& !ExecutionStatus.SU.equals(stateInstance.getStatus())) {
return stateInstance;
}
}
return lastForwardState;
}
public static StateInstance findOutLastRetriedStateInstance(StateMachineInstance stateMachineInstance,
String stateName) {
List<StateInstance> actList = stateMachineInstance.getStateList();
for (int i = actList.size() - 1; i >= 0; i--) {
StateInstance stateInstance = actList.get(i);
if (stateInstance.getName().equals(stateName)) {
return stateInstance;
}
}
return null;
}
/**
* check if satisfied completion condition
*
* @param context
* @return
*/
public static boolean isCompletionConditionSatisfied(ProcessContext context) {
StateInstruction instruction = context.getInstruction(StateInstruction.class);
AbstractTaskState currentState = (AbstractTaskState)instruction.getState(context);
LoopContextHolder currentLoopContext = LoopContextHolder.getCurrent(context, true);
if (currentLoopContext.isCompletionConditionSatisfied()) {
return true;
}
int nrOfInstances = currentLoopContext.getNrOfInstances().get();
int nrOfActiveInstances = currentLoopContext.getNrOfActiveInstances().get();
int nrOfCompletedInstances = currentLoopContext.getNrOfCompletedInstances().get();
if (!currentLoopContext.isCompletionConditionSatisfied()) {
synchronized (currentLoopContext) {
if (!currentLoopContext.isCompletionConditionSatisfied()) {
Map<String, Object> stateMachineContext = (Map<String, Object>)context.getVariable(
DomainConstants.VAR_NAME_STATEMACHINE_CONTEXT);
// multi-instance variables should be double/float while evaluate
stateMachineContext.put(DomainConstants.NUMBER_OF_INSTANCES, (double)nrOfInstances);
stateMachineContext.put(DomainConstants.NUMBER_OF_ACTIVE_INSTANCES, (double)nrOfActiveInstances);
stateMachineContext.put(DomainConstants.NUMBER_OF_COMPLETED_INSTANCES,
(double)nrOfCompletedInstances);
if (nrOfCompletedInstances >= nrOfInstances || getEvaluator(context,
currentState.getLoop().getCompletionCondition()).evaluate(stateMachineContext)) {
currentLoopContext.setCompletionConditionSatisfied(true);
}
}
}
}
return currentLoopContext.isCompletionConditionSatisfied();
}
public static int acquireNextLoopCounter(ProcessContext context) {
try {
return LoopContextHolder.getCurrent(context, true).getLoopCounterStack().pop();
} catch (EmptyStackException e) {
return -1;
}
}
/**
* generate loop state name like stateName-fork-1
*
* @param stateName
* @param context
* @return
*/
public static String generateLoopStateName(ProcessContext context, String stateName) {
if (StringUtils.isNotBlank(stateName)) {
int loopCounter = (int)context.getVariable(DomainConstants.LOOP_COUNTER);
return stateName + LOOP_STATE_NAME_PATTERN + loopCounter;
}
return stateName;
}
/**
* reload context loop counter from stateInstName
*
* @param stateName
* @return
* @see #generateLoopStateName(ProcessContext, String)
*/
public static int reloadLoopCounter(String stateName) {
if (StringUtils.isNotBlank(stateName)) {
int end = stateName.lastIndexOf(LOOP_STATE_NAME_PATTERN);
if (end > -1) {
String loopCounter = stateName.substring(end + LOOP_STATE_NAME_PATTERN.length());
return NumberUtils.toInt(loopCounter, -1);
}
}
return -1;
}
/**
* put loop out params to parent context
*
* @param context
*/
public static void putContextToParent(ProcessContext context, List<ProcessContext> subContextList, State state) {
Map<String, Object> contextVariables = (Map<String, Object>)context.getVariable(
DomainConstants.VAR_NAME_STATEMACHINE_CONTEXT);
if (CollectionUtils.isNotEmpty(subContextList)) {
StateMachineConfig stateMachineConfig = (StateMachineConfig)context.getVariable(
DomainConstants.VAR_NAME_STATEMACHINE_CONFIG);
List<Map<String, Object>> subContextVariables = new ArrayList<>();
for (ProcessContext subProcessContext : subContextList) {
StateInstance stateInstance = (StateInstance)subProcessContext.getVariable(DomainConstants.VAR_NAME_STATE_INST);
Map<String, Object> outputVariablesToContext = ParameterUtils.createOutputParams(
stateMachineConfig.getExpressionFactoryManager(), (AbstractTaskState)state, stateInstance.getOutputParams());
subContextVariables.add(outputVariablesToContext);
}
contextVariables.put(DomainConstants.LOOP_RESULT, subContextVariables);
}
}
/**
* forward with subStateMachine should check each loop state's status
*
* @param context
* @return
*/
public static boolean isForSubStateMachineForward(ProcessContext context) {
StateMachineInstance stateMachineInstance = (StateMachineInstance)context.getVariable(
DomainConstants.VAR_NAME_STATEMACHINE_INST);
StateInstruction instruction = context.getInstruction(StateInstruction.class);
StateMachineConfig stateMachineConfig = (StateMachineConfig)context.getVariable(
DomainConstants.VAR_NAME_STATEMACHINE_CONFIG);
StateInstance lastRetriedStateInstance = LoopTaskUtils.findOutLastRetriedStateInstance(
stateMachineInstance, LoopTaskUtils.generateLoopStateName(context, instruction.getStateName()));
if (null != lastRetriedStateInstance && DomainConstants.STATE_TYPE_SUB_STATE_MACHINE.equals(
lastRetriedStateInstance.getType()) && !ExecutionStatus.SU.equals(
lastRetriedStateInstance.getCompensationStatus())) {
while (StringUtils.isNotBlank(lastRetriedStateInstance.getStateIdRetriedFor())) {
lastRetriedStateInstance = stateMachineConfig.getStateLogStore().getStateInstance(
lastRetriedStateInstance.getStateIdRetriedFor(), lastRetriedStateInstance.getMachineInstanceId());
}
List<StateMachineInstance> subInst = stateMachineConfig.getStateLogStore()
.queryStateMachineInstanceByParentId(EngineUtils.generateParentId(lastRetriedStateInstance));
if (CollectionUtils.isNotEmpty(subInst)) {
if (ExecutionStatus.SU.equals(subInst.get(0).getCompensationStatus())) {
return false;
}
}
if (ExecutionStatus.UN.equals(subInst.get(0).getCompensationStatus())) {
throw new ForwardInvalidException(
"Last forward execution state instance is SubStateMachine and compensation status is "
+ "[UN], Operation[forward] denied, stateInstanceId:"
+ lastRetriedStateInstance.getId(), FrameworkErrorCode.OperationDenied);
}
return true;
}
return false;
}
/**
* decide current exception route for loop publish over
*
* @param subContextList
* @param stateMachine
* @return route if current exception route not null
*/
public static String decideCurrentExceptionRoute(List<ProcessContext> subContextList, StateMachine stateMachine) {
String route = null;
if (CollectionUtils.isNotEmpty(subContextList)) {
for (ProcessContext processContext : subContextList) {
String next = (String)processContext.getVariable(DomainConstants.VAR_NAME_CURRENT_EXCEPTION_ROUTE);
if (StringUtils.isNotBlank(next)) {
// compensate must be execute
State state = stateMachine.getState(next);
if (DomainConstants.STATE_TYPE_COMPENSATION_TRIGGER.equals(state.getType())) {
route = next;
break;
} else if (null == route) {
route = next;
}
}
}
}
return route;
}
/**
* get loop completion condition evaluator
*
* @param context
* @param completionCondition
* @return
*/
private static ExpressionEvaluator getEvaluator(ProcessContext context, String completionCondition) {
if (StringUtils.isBlank(completionCondition)) {
completionCondition = DEFAULT_COMPLETION_CONDITION;
}
if (!EXPRESSION_EVALUATOR_MAP.containsKey(completionCondition)) {
StateMachineConfig stateMachineConfig = (StateMachineConfig)context.getVariable(
DomainConstants.VAR_NAME_STATEMACHINE_CONFIG);
ExpressionEvaluator expressionEvaluator = (ExpressionEvaluator)stateMachineConfig
.getEvaluatorFactoryManager().getEvaluatorFactory(EvaluatorFactoryManager.EVALUATOR_TYPE_DEFAULT)
.createEvaluator(completionCondition);
expressionEvaluator.setRootObjectName(null);
EXPRESSION_EVALUATOR_MAP.put(completionCondition, expressionEvaluator);
}
return EXPRESSION_EVALUATOR_MAP.get(completionCondition);
}
private static StateInstruction copyInstruction(StateInstruction instruction) {
StateInstruction targetInstruction = new StateInstruction();
targetInstruction.setStateMachineName(instruction.getStateMachineName());
targetInstruction.setTenantId(instruction.getTenantId());
targetInstruction.setStateName(instruction.getStateName());
targetInstruction.setTemporaryState(instruction.getTemporaryState());
return targetInstruction;
}
}

View File

@@ -0,0 +1,177 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.pcext.utils;
import io.seata.common.util.CollectionUtils;
import io.seata.saga.engine.expression.Expression;
import io.seata.saga.engine.expression.ExpressionFactory;
import io.seata.saga.engine.expression.ExpressionFactoryManager;
import io.seata.saga.engine.expression.seq.SequenceExpression;
import io.seata.saga.statelang.domain.StateInstance;
import io.seata.saga.statelang.domain.impl.AbstractTaskState;
import io.seata.saga.statelang.domain.impl.StateInstanceImpl;
import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
*
* ParameterUtils
*
* @author lorne.cl
*/
public class ParameterUtils {
public static List<Object> createInputParams(ExpressionFactoryManager expressionFactoryManager,
StateInstanceImpl stateInstance,
AbstractTaskState serviceTaskState, Object variablesFrom) {
List<Object> inputAssignments = serviceTaskState.getInput();
if (CollectionUtils.isEmpty(inputAssignments)) {
return new ArrayList<>(0);
}
List<Object> inputExpressions = serviceTaskState.getInputExpressions();
if (inputExpressions == null) {
synchronized (serviceTaskState) {
inputExpressions = serviceTaskState.getInputExpressions();
if (inputExpressions == null) {
inputExpressions = new ArrayList<>(inputAssignments.size());
for (Object inputAssignment : inputAssignments) {
inputExpressions.add(createValueExpression(expressionFactoryManager, inputAssignment));
}
}
serviceTaskState.setInputExpressions(inputExpressions);
}
}
List<Object> inputValues = new ArrayList<>(inputExpressions.size());
for (Object valueExpression : inputExpressions) {
Object value = getValue(valueExpression, variablesFrom, stateInstance);
inputValues.add(value);
}
return inputValues;
}
public static Map<String, Object> createOutputParams(ExpressionFactoryManager expressionFactoryManager,
AbstractTaskState serviceTaskState, Object variablesFrom) {
Map<String, Object> outputAssignments = serviceTaskState.getOutput();
if (CollectionUtils.isEmpty(outputAssignments)) {
return new LinkedHashMap<>(0);
}
Map<String, Object> outputExpressions = serviceTaskState.getOutputExpressions();
if (outputExpressions == null) {
synchronized (serviceTaskState) {
outputExpressions = serviceTaskState.getOutputExpressions();
if (outputExpressions == null) {
outputExpressions = new LinkedHashMap<>(outputAssignments.size());
for (Map.Entry<String, Object> entry : outputAssignments.entrySet()) {
outputExpressions.put(entry.getKey(),
createValueExpression(expressionFactoryManager, entry.getValue()));
}
}
serviceTaskState.setOutputExpressions(outputExpressions);
}
}
Map<String, Object> outputValues = new LinkedHashMap<>(outputExpressions.size());
for (String paramName : outputExpressions.keySet()) {
outputValues.put(paramName, getValue(outputExpressions.get(paramName), variablesFrom, null));
}
return outputValues;
}
public static Object getValue(Object valueExpression, Object variablesFrom, StateInstance stateInstance) {
if (valueExpression instanceof Expression) {
Object value = ((Expression)valueExpression).getValue(variablesFrom);
if (value != null && stateInstance != null && StringUtils.isEmpty(stateInstance.getBusinessKey())
&& valueExpression instanceof SequenceExpression) {
stateInstance.setBusinessKey(String.valueOf(value));
}
return value;
} else if (valueExpression instanceof Map) {
Map<String, Object> mapValueExpression = (Map<String, Object>)valueExpression;
Map<String, Object> mapValue = new LinkedHashMap<>();
mapValueExpression.forEach((key, value) -> {
value = getValue(value, variablesFrom, stateInstance);
if (value != null) {
mapValue.put(key, value);
}
});
return mapValue;
} else if (valueExpression instanceof List) {
List<Object> listValueExpression = (List<Object>)valueExpression;
List<Object> listValue = new ArrayList<>(listValueExpression.size());
for (Object aValueExpression : listValueExpression) {
listValue.add(getValue(aValueExpression, variablesFrom, stateInstance));
}
return listValue;
} else {
return valueExpression;
}
}
public static Object createValueExpression(ExpressionFactoryManager expressionFactoryManager,
Object paramAssignment) {
Object valueExpression;
if (paramAssignment instanceof Expression) {
valueExpression = paramAssignment;
} else if (paramAssignment instanceof Map) {
Map<String, Object> paramMapAssignment = (Map<String, Object>)paramAssignment;
Map<String, Object> paramMap = new LinkedHashMap<>(paramMapAssignment.size());
paramMapAssignment.forEach((paramName, valueAssignment) -> {
paramMap.put(paramName, createValueExpression(expressionFactoryManager, valueAssignment));
});
valueExpression = paramMap;
} else if (paramAssignment instanceof List) {
List<Object> paramListAssignment = (List<Object>)paramAssignment;
List<Object> paramList = new ArrayList<>(paramListAssignment.size());
for (Object aParamAssignment : paramListAssignment) {
paramList.add(createValueExpression(expressionFactoryManager, aParamAssignment));
}
valueExpression = paramList;
} else if (paramAssignment instanceof String && ((String)paramAssignment).startsWith("$")) {
String expressionStr = (String)paramAssignment;
int expTypeStart = expressionStr.indexOf("$");
int expTypeEnd = expressionStr.indexOf(".", expTypeStart);
String expressionType = null;
if (expTypeStart >= 0 && expTypeEnd > expTypeStart) {
expressionType = expressionStr.substring(expTypeStart + 1, expTypeEnd);
}
int expEnd = expressionStr.length();
String expressionContent = null;
if (expTypeEnd > 0 && expEnd > expTypeEnd) {
expressionContent = expressionStr.substring(expTypeEnd + 1, expEnd);
}
ExpressionFactory expressionFactory = expressionFactoryManager.getExpressionFactory(expressionType);
if (expressionFactory == null) {
throw new IllegalArgumentException("Cannot get ExpressionFactory by Type[" + expressionType + "]");
}
valueExpression = expressionFactory.createExpression(expressionContent);
} else {
valueExpression = paramAssignment;
}
return valueExpression;
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.repo;
import java.util.List;
import io.seata.saga.statelang.domain.StateInstance;
import io.seata.saga.statelang.domain.StateMachineInstance;
/**
* State Log Repository
*
* @author lorne.cl
*/
public interface StateLogRepository {
/**
* Get state machine instance
*
* @param stateMachineInstanceId
* @return
*/
StateMachineInstance getStateMachineInstance(String stateMachineInstanceId);
/**
* Get state machine instance by businessKey
*
* @param businessKey
* @param tenantId
* @return
*/
StateMachineInstance getStateMachineInstanceByBusinessKey(String businessKey, String tenantId);
/**
* Query the list of state machine instances by parent id
*
* @param parentId
* @return
*/
List<StateMachineInstance> queryStateMachineInstanceByParentId(String parentId);
/**
* Get state instance
*
* @param stateInstanceId
* @param machineInstId
* @return
*/
StateInstance getStateInstance(String stateInstanceId, String machineInstId);
/**
* Get a list of state instances by state machine instance id
*
* @param stateMachineInstanceId
* @return
*/
List<StateInstance> queryStateInstanceListByMachineInstanceId(String stateMachineInstanceId);
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.repo;
import java.io.IOException;
import io.seata.saga.statelang.domain.StateMachine;
import org.springframework.core.io.Resource;
/**
* StateMachineRepository
*
* @author lorne.cl
*/
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 registryStateMachine(StateMachine stateMachine);
/**
* registry by resources
*
* @param resources
* @param tenantId
*/
void registryByResources(Resource[] resources, String tenantId) throws IOException;
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.repo.impl;
import java.util.List;
import io.seata.saga.engine.repo.StateLogRepository;
import io.seata.saga.engine.store.StateLogStore;
import io.seata.saga.statelang.domain.StateInstance;
import io.seata.saga.statelang.domain.StateMachineInstance;
/**
* State Log Repository
*
* @author lorne.cl
*/
public class StateLogRepositoryImpl implements StateLogRepository {
private StateLogStore stateLogStore;
@Override
public StateMachineInstance getStateMachineInstance(String stateMachineInstanceId) {
if (stateLogStore == null) {
return null;
}
return stateLogStore.getStateMachineInstance(stateMachineInstanceId);
}
@Override
public StateMachineInstance getStateMachineInstanceByBusinessKey(String businessKey, String tenantId) {
if (stateLogStore == null) {
return null;
}
return stateLogStore.getStateMachineInstanceByBusinessKey(businessKey, tenantId);
}
@Override
public List<StateMachineInstance> queryStateMachineInstanceByParentId(String parentId) {
if (stateLogStore == null) {
return null;
}
return stateLogStore.queryStateMachineInstanceByParentId(parentId);
}
@Override
public StateInstance getStateInstance(String stateInstanceId, String machineInstId) {
if (stateLogStore == null) {
return null;
}
return stateLogStore.getStateInstance(stateInstanceId, machineInstId);
}
@Override
public List<StateInstance> queryStateInstanceListByMachineInstanceId(String stateMachineInstanceId) {
if (stateLogStore == null) {
return null;
}
return stateLogStore.queryStateInstanceListByMachineInstanceId(stateMachineInstanceId);
}
public void setStateLogStore(StateLogStore stateLogStore) {
this.stateLogStore = stateLogStore;
}
}

View File

@@ -0,0 +1,236 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.repo.impl;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import io.seata.common.util.CollectionUtils;
import io.seata.common.util.StringUtils;
import io.seata.saga.engine.repo.StateMachineRepository;
import io.seata.saga.engine.sequence.SeqGenerator;
import io.seata.saga.engine.sequence.SpringJvmUUIDSeqGenerator;
import io.seata.saga.engine.store.StateLangStore;
import io.seata.saga.statelang.domain.DomainConstants;
import io.seata.saga.statelang.domain.StateMachine;
import io.seata.saga.statelang.parser.StateMachineParserFactory;
import io.seata.saga.statelang.parser.utils.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.Resource;
/**
* StateMachineRepository Implementation
*
* @author lorne.cl
*/
public class StateMachineRepositoryImpl implements StateMachineRepository {
private static final Logger LOGGER = LoggerFactory.getLogger(StateMachineRepositoryImpl.class);
private Map<String/** Name_Tenant **/, Item> stateMachineMapByNameAndTenant = new ConcurrentHashMap<>();
private Map<String/** Id **/, Item> stateMachineMapById = new ConcurrentHashMap<>();
private StateLangStore stateLangStore;
private SeqGenerator seqGenerator = new SpringJvmUUIDSeqGenerator();
private String charset = "UTF-8";
private String defaultTenantId;
private String jsonParserName = DomainConstants.DEFAULT_JSON_PARSER;
@Override
public StateMachine getStateMachineById(String stateMachineId) {
Item item = CollectionUtils.computeIfAbsent(stateMachineMapById, stateMachineId,
key -> new Item());
if (item.getValue() == null && stateLangStore != null) {
synchronized (item) {
if (item.getValue() == null && stateLangStore != null) {
StateMachine stateMachine = stateLangStore.getStateMachineById(stateMachineId);
if (stateMachine != null) {
StateMachine parsedStatMachine = StateMachineParserFactory.getStateMachineParser(jsonParserName).parse(
stateMachine.getContent());
if (parsedStatMachine == null) {
throw new RuntimeException(
"Parse State Language failed, stateMachineId:" + stateMachine.getId() + ", name:"
+ stateMachine.getName());
}
stateMachine.setStartState(parsedStatMachine.getStartState());
stateMachine.getStates().putAll(parsedStatMachine.getStates());
item.setValue(stateMachine);
stateMachineMapById.put(stateMachine.getName() + "_" + stateMachine.getTenantId(),
item);
}
}
}
}
return item.getValue();
}
@Override
public StateMachine getStateMachine(String stateMachineName, String tenantId) {
Item item = CollectionUtils.computeIfAbsent(stateMachineMapByNameAndTenant, stateMachineName + "_" + tenantId,
key -> new Item());
if (item.getValue() == null && stateLangStore != null) {
synchronized (item) {
if (item.getValue() == null && stateLangStore != null) {
StateMachine stateMachine = stateLangStore.getLastVersionStateMachine(stateMachineName, tenantId);
if (stateMachine != null) {
StateMachine parsedStatMachine = StateMachineParserFactory.getStateMachineParser(jsonParserName).parse(
stateMachine.getContent());
if (parsedStatMachine == null) {
throw new RuntimeException(
"Parse State Language failed, stateMachineId:" + stateMachine.getId() + ", name:"
+ stateMachine.getName());
}
stateMachine.setStartState(parsedStatMachine.getStartState());
stateMachine.getStates().putAll(parsedStatMachine.getStates());
item.setValue(stateMachine);
stateMachineMapById.put(stateMachine.getId(), item);
}
}
}
}
return item.getValue();
}
@Override
public StateMachine getStateMachine(String stateMachineName, String tenantId, String version) {
throw new UnsupportedOperationException("not implement yet");
}
@Override
public StateMachine registryStateMachine(StateMachine stateMachine) {
String stateMachineName = stateMachine.getName();
String tenantId = stateMachine.getTenantId();
if (stateLangStore != null) {
StateMachine oldStateMachine = stateLangStore.getLastVersionStateMachine(stateMachineName, tenantId);
if (oldStateMachine != null) {
byte[] oldBytesContent = null;
byte[] bytesContent = null;
try {
oldBytesContent = oldStateMachine.getContent().getBytes(charset);
bytesContent = stateMachine.getContent().getBytes(charset);
} catch (UnsupportedEncodingException e) {
LOGGER.error(e.getMessage(), e);
}
if (Arrays.equals(bytesContent, oldBytesContent) && stateMachine.getVersion() != null && stateMachine
.getVersion().equals(oldStateMachine.getVersion())) {
LOGGER.info("StateMachine[{}] is already exist a same version", stateMachineName);
stateMachine.setId(oldStateMachine.getId());
stateMachine.setGmtCreate(oldStateMachine.getGmtCreate());
Item item = new Item(stateMachine);
stateMachineMapByNameAndTenant.put(stateMachineName + "_" + tenantId, item);
stateMachineMapById.put(stateMachine.getId(), item);
return stateMachine;
}
}
if (StringUtils.isBlank(stateMachine.getId())) {
stateMachine.setId(seqGenerator.generate(DomainConstants.SEQ_ENTITY_STATE_MACHINE));
}
stateMachine.setGmtCreate(new Date());
stateLangStore.storeStateMachine(stateMachine);
}
if (StringUtils.isBlank(stateMachine.getId())) {
stateMachine.setId(seqGenerator.generate(DomainConstants.SEQ_ENTITY_STATE_MACHINE));
}
Item item = new Item(stateMachine);
stateMachineMapByNameAndTenant.put(stateMachineName + "_" + tenantId, item);
stateMachineMapById.put(stateMachine.getId(), item);
return stateMachine;
}
@Override
public void registryByResources(Resource[] resources, String tenantId) throws IOException {
if (resources != null) {
for (Resource resource : resources) {
String json = IOUtils.toString(resource.getInputStream(), charset);
StateMachine stateMachine = StateMachineParserFactory.getStateMachineParser(jsonParserName).parse(json);
if (stateMachine != null) {
stateMachine.setContent(json);
if (StringUtils.isBlank(stateMachine.getTenantId())) {
stateMachine.setTenantId(tenantId);
}
registryStateMachine(stateMachine);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("===== StateMachine Loaded: \n{}", json);
}
}
}
}
}
public void setStateLangStore(StateLangStore stateLangStore) {
this.stateLangStore = stateLangStore;
}
public void setSeqGenerator(SeqGenerator seqGenerator) {
this.seqGenerator = seqGenerator;
}
public String getCharset() {
return charset;
}
public void setCharset(String charset) {
this.charset = charset;
}
public String getDefaultTenantId() {
return defaultTenantId;
}
public void setDefaultTenantId(String defaultTenantId) {
this.defaultTenantId = defaultTenantId;
}
public String getJsonParserName() {
return jsonParserName;
}
public void setJsonParserName(String jsonParserName) {
this.jsonParserName = jsonParserName;
}
private static class Item {
private StateMachine value;
private Item() {
}
private Item(StateMachine value) {
this.value = value;
}
public StateMachine getValue() {
return value;
}
public void setValue(StateMachine value) {
this.value = value;
}
}
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.sequence;
import java.util.List;
/**
* SeqGenerator
*
* @author lorne.cl
*/
public interface SeqGenerator {
/**
* Generate string.
*
* @param entity the entity
* @return the string
*/
String generate(String entity);
/**
* Generate string.
*
* @param entity the entity
* @param shardingParameters the sharding parameters
* @return the string
*/
String generate(String entity, List<Object> shardingParameters);
/**
* Generate string.
*
* @param entity the entity
* @param ruleName the rule name
* @param shardingParameters the sharding parameters
* @return the string
*/
String generate(String entity, String ruleName, List<Object> shardingParameters);
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.sequence;
import java.util.List;
import org.springframework.util.AlternativeJdkIdGenerator;
import org.springframework.util.IdGenerator;
/**
* Based On Spring AlternativeJdkIdGenerator
*
* @author lorne.cl
*/
public class SpringJvmUUIDSeqGenerator implements SeqGenerator {
private IdGenerator idGenerator = new AlternativeJdkIdGenerator();
@Override
public String generate(String entity, String ruleName, List<Object> shardingParameters) {
String uuid = idGenerator.generateId().toString();
StringBuilder sb = new StringBuilder(uuid.length() - 4);
for (String seg : uuid.split("-")) {
sb.append(seg);
}
return sb.toString();
}
@Override
public String generate(String entity, List<Object> shardingParameters) {
return generate(entity, null, shardingParameters);
}
@Override
public String generate(String entity) {
return generate(entity, null);
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.store;
import io.seata.saga.statelang.domain.StateMachine;
/**
* State language definition store
*
* @author lorne.cl
*/
public interface StateLangStore {
/**
* Query the state machine definition by id
*
* @param stateMachineId
* @return
*/
StateMachine getStateMachineById(String stateMachineId);
/**
* Get the latest version of the state machine by state machine name
*
* @param stateMachineName
* @param tenantId
* @return
*/
StateMachine getLastVersionStateMachine(String stateMachineName, String tenantId);
/**
* Storage state machine definition
*
* @param stateMachine
* @return
*/
boolean storeStateMachine(StateMachine stateMachine);
}

View File

@@ -0,0 +1,107 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.store;
import java.util.List;
import io.seata.saga.proctrl.ProcessContext;
import io.seata.saga.statelang.domain.StateInstance;
import io.seata.saga.statelang.domain.StateMachineInstance;
/**
* StateMachine engine log store
*
* @author lorne.cl
*/
public interface StateLogStore {
/**
* Record state machine startup events
*
* @param machineInstance
*/
void recordStateMachineStarted(StateMachineInstance machineInstance, ProcessContext context);
/**
* Record status end event
*
* @param machineInstance
*/
void recordStateMachineFinished(StateMachineInstance machineInstance, ProcessContext context);
/**
* Record state machine restarted
*
* @param machineInstance
*/
void recordStateMachineRestarted(StateMachineInstance machineInstance, ProcessContext context);
/**
* Record state start execution event
*
* @param stateInstance
*/
void recordStateStarted(StateInstance stateInstance, ProcessContext context);
/**
* Record state execution end event
*
* @param stateInstance
*/
void recordStateFinished(StateInstance stateInstance, ProcessContext context);
/**
* Get state machine instance
*
* @param stateMachineInstanceId
* @return
*/
StateMachineInstance getStateMachineInstance(String stateMachineInstanceId);
/**
* Get state machine instance by businessKey
*
* @param businessKey
* @param tenantId
* @return
*/
StateMachineInstance getStateMachineInstanceByBusinessKey(String businessKey, String tenantId);
/**
* Query the list of state machine instances by parent id
*
* @param parentId
* @return
*/
List<StateMachineInstance> queryStateMachineInstanceByParentId(String parentId);
/**
* Get state instance
*
* @param stateInstanceId
* @param machineInstId
* @return
*/
StateInstance getStateInstance(String stateInstanceId, String machineInstId);
/**
* Get a list of state instances by state machine instance id
*
* @param stateMachineInstanceId
* @return
*/
List<StateInstance> queryStateInstanceListByMachineInstanceId(String stateMachineInstanceId);
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.strategy;
import io.seata.saga.proctrl.ProcessContext;
import io.seata.saga.statelang.domain.StateMachineInstance;
/**
* Default state machine execution status decision strategy.
* The strategy is to traverse the execution state of each state executed.
* If all state are successfully executed the state machine is successfully executed,
* if there is a state that fails to execute which is for data update, the state machine execution status is considered
* to be UN (the data is inconsistent),
* otherwise FA (failure: no data inconsistency)
*
* @author lorne.cl
*/
public interface StatusDecisionStrategy {
/**
* Determine state machine execution status when executing to EndState
*
* @param context
* @param stateMachineInstance
* @param exp
*/
void decideOnEndState(ProcessContext context, StateMachineInstance stateMachineInstance, Exception exp);
/**
* Determine state machine execution status when executing TaskState error
*
* @param context
* @param stateMachineInstance
* @param exp
*/
void decideOnTaskStateFail(ProcessContext context, StateMachineInstance stateMachineInstance, Exception exp);
/**
* Determine the forward execution state of the state machine
*
* @param stateMachineInstance
* @param exp
* @param specialPolicy
* @return
*/
boolean decideMachineForwardExecutionStatus(StateMachineInstance stateMachineInstance, Exception exp,
boolean specialPolicy);
}

View File

@@ -0,0 +1,244 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.strategy.impl;
import java.util.List;
import io.seata.common.exception.FrameworkErrorCode;
import io.seata.common.util.CollectionUtils;
import io.seata.saga.engine.exception.EngineExecutionException;
import io.seata.saga.engine.pcext.utils.CompensationHolder;
import io.seata.saga.engine.strategy.StatusDecisionStrategy;
import io.seata.saga.engine.utils.ExceptionUtils;
import io.seata.saga.engine.utils.ExceptionUtils.NetExceptionType;
import io.seata.saga.proctrl.ProcessContext;
import io.seata.saga.statelang.domain.DomainConstants;
import io.seata.saga.statelang.domain.ExecutionStatus;
import io.seata.saga.statelang.domain.StateInstance;
import io.seata.saga.statelang.domain.StateMachineInstance;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Default state machine execution status decision strategy
*
* @author lorne.cl
* @see StatusDecisionStrategy
*/
public class DefaultStatusDecisionStrategy implements StatusDecisionStrategy {
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultStatusDecisionStrategy.class);
/**
* decide machine compensate status
*
* @param stateMachineInstance
* @param compensationHolder
*/
public static void decideMachineCompensateStatus(StateMachineInstance stateMachineInstance,
CompensationHolder compensationHolder) {
if (stateMachineInstance.getStatus() == null || ExecutionStatus.RU.equals(stateMachineInstance.getStatus())) {
stateMachineInstance.setStatus(ExecutionStatus.UN);
}
if (!compensationHolder.getStateStackNeedCompensation().isEmpty()) {
boolean hasCompensateSUorUN = false;
for (StateInstance forCompensateState : compensationHolder.getStatesForCompensation().values()) {
if (ExecutionStatus.UN.equals(forCompensateState.getStatus()) || ExecutionStatus.SU.equals(
forCompensateState.getStatus())) {
hasCompensateSUorUN = true;
break;
}
}
if (hasCompensateSUorUN) {
stateMachineInstance.setCompensationStatus(ExecutionStatus.UN);
} else {
stateMachineInstance.setCompensationStatus(ExecutionStatus.FA);
}
} else {
boolean hasCompensateError = false;
for (StateInstance forCompensateState : compensationHolder.getStatesForCompensation().values()) {
if (!ExecutionStatus.SU.equals(forCompensateState.getStatus())) {
hasCompensateError = true;
break;
}
}
if (hasCompensateError) {
stateMachineInstance.setCompensationStatus(ExecutionStatus.UN);
} else {
stateMachineInstance.setCompensationStatus(ExecutionStatus.SU);
}
}
}
/**
* set machine status based on state list
*
* @param stateMachineInstance
* @param stateList
* @return
*/
public static void setMachineStatusBasedOnStateListAndException(StateMachineInstance stateMachineInstance,
List<StateInstance> stateList, Exception exp) {
boolean hasSetStatus = false;
boolean hasSuccessUpdateService = false;
if (CollectionUtils.isNotEmpty(stateList)) {
boolean hasUnsuccessService = false;
for (int i = stateList.size() - 1; i >= 0; i--) {
StateInstance stateInstance = stateList.get(i);
if (stateInstance.isIgnoreStatus() || stateInstance.isForCompensation()) {
continue;
}
if (ExecutionStatus.UN.equals(stateInstance.getStatus())) {
stateMachineInstance.setStatus(ExecutionStatus.UN);
hasSetStatus = true;
} else if (ExecutionStatus.SU.equals(stateInstance.getStatus())) {
if (DomainConstants.STATE_TYPE_SERVICE_TASK.equals(stateInstance.getType())) {
if (stateInstance.isForUpdate() && !stateInstance.isForCompensation()) {
hasSuccessUpdateService = true;
}
}
} else if (ExecutionStatus.SK.equals(stateInstance.getStatus())) {
// ignore
} else {
hasUnsuccessService = true;
}
}
if (!hasSetStatus && hasUnsuccessService) {
if (hasSuccessUpdateService) {
stateMachineInstance.setStatus(ExecutionStatus.UN);
} else {
stateMachineInstance.setStatus(ExecutionStatus.FA);
}
hasSetStatus = true;
}
}
if (!hasSetStatus) {
setMachineStatusBasedOnException(stateMachineInstance, exp, hasSuccessUpdateService);
}
}
/**
* set machine status based on net exception
*
* @param stateMachineInstance
* @param exp
*/
public static void setMachineStatusBasedOnException(StateMachineInstance stateMachineInstance, Exception exp,
boolean hasSuccessUpdateService) {
if (exp == null) {
stateMachineInstance.setStatus(ExecutionStatus.SU);
} else if (exp instanceof EngineExecutionException
&& FrameworkErrorCode.StateMachineExecutionTimeout.equals(((EngineExecutionException)exp).getErrcode())) {
stateMachineInstance.setStatus(ExecutionStatus.UN);
} else if (hasSuccessUpdateService) {
stateMachineInstance.setStatus(ExecutionStatus.UN);
} else {
NetExceptionType t = ExceptionUtils.getNetExceptionType(exp);
if (t != null) {
if (t.equals(NetExceptionType.CONNECT_EXCEPTION) || t.equals(NetExceptionType.CONNECT_TIMEOUT_EXCEPTION)
|| t.equals(NetExceptionType.NOT_NET_EXCEPTION)) {
stateMachineInstance.setStatus(ExecutionStatus.FA);
} else if (t.equals(NetExceptionType.READ_TIMEOUT_EXCEPTION)) {
stateMachineInstance.setStatus(ExecutionStatus.UN);
}
} else {
stateMachineInstance.setStatus(ExecutionStatus.UN);
}
}
}
@Override
public void decideOnEndState(ProcessContext context, StateMachineInstance stateMachineInstance, Exception exp) {
if (ExecutionStatus.RU.equals(stateMachineInstance.getCompensationStatus())) {
CompensationHolder compensationHolder = CompensationHolder.getCurrent(context, true);
decideMachineCompensateStatus(stateMachineInstance, compensationHolder);
} else {
Object failEndStateFlag = context.getVariable(DomainConstants.VAR_NAME_FAIL_END_STATE_FLAG);
boolean isComeFromFailEndState = failEndStateFlag != null && (Boolean)failEndStateFlag;
decideMachineForwardExecutionStatus(stateMachineInstance, exp, isComeFromFailEndState);
}
if (stateMachineInstance.getCompensationStatus() != null && DomainConstants.OPERATION_NAME_FORWARD.equals(
context.getVariable(DomainConstants.VAR_NAME_OPERATION_NAME)) && ExecutionStatus.SU.equals(
stateMachineInstance.getStatus())) {
stateMachineInstance.setCompensationStatus(ExecutionStatus.FA);
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
"StateMachine Instance[id:{},name:{}] execute finish with status[{}], compensation status [{}].",
stateMachineInstance.getId(), stateMachineInstance.getStateMachine().getName(),
stateMachineInstance.getStatus(), stateMachineInstance.getCompensationStatus());
}
}
@Override
public void decideOnTaskStateFail(ProcessContext context, StateMachineInstance stateMachineInstance,
Exception exp) {
if (!decideMachineForwardExecutionStatus(stateMachineInstance, exp, true)) {
stateMachineInstance.setCompensationStatus(ExecutionStatus.UN);
}
}
/**
* Determine the forward execution state of the state machine
*
* @param stateMachineInstance
* @param exp
* @param specialPolicy
* @return
*/
@Override
public boolean decideMachineForwardExecutionStatus(StateMachineInstance stateMachineInstance, Exception exp,
boolean specialPolicy) {
boolean result = false;
if (stateMachineInstance.getStatus() == null || ExecutionStatus.RU.equals(stateMachineInstance.getStatus())) {
result = true;
List<StateInstance> stateList = stateMachineInstance.getStateList();
setMachineStatusBasedOnStateListAndException(stateMachineInstance, stateList, exp);
if (specialPolicy && ExecutionStatus.SU.equals(stateMachineInstance.getStatus())) {
for (StateInstance stateInstance : stateMachineInstance.getStateList()) {
if (!stateInstance.isIgnoreStatus() && (stateInstance.isForUpdate() || stateInstance
.isForCompensation())) {
stateMachineInstance.setStatus(ExecutionStatus.UN);
break;
}
}
if (ExecutionStatus.SU.equals(stateMachineInstance.getStatus())) {
stateMachineInstance.setStatus(ExecutionStatus.FA);
}
}
}
return result;
}
}

View File

@@ -0,0 +1,140 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.utils;
import io.seata.common.exception.FrameworkErrorCode;
import io.seata.saga.engine.exception.EngineExecutionException;
import io.seata.saga.statelang.domain.StateInstance;
import io.seata.saga.statelang.domain.StateMachineInstance;
/**
* Exception Utils
*
* @author lorne.cl
*/
public class ExceptionUtils {
public static final String CONNECT_TIMED_OUT = "connect timed out";
public static final String CONNECT_TIME_OUT_EXCEPTION_CLASS_NAME = "ConnectTimeoutException";
public static final String READ_TIME_OUT_EXCEPTION_CLASS_NAME = "ReadTimeoutException";
public static final String CONNECT_EXCEPTION_CLASS_NAME = "ConnectException";
public static final int MAX_CAUSE_DEP = 20;
public static EngineExecutionException createEngineExecutionException(Exception e, FrameworkErrorCode code,
String message,
StateMachineInstance stateMachineInstance,
StateInstance stateInstance) {
EngineExecutionException exception = new EngineExecutionException(e, message, code);
if (stateMachineInstance != null) {
exception.setStateMachineName(stateMachineInstance.getStateMachine().getAppName());
exception.setStateMachineInstanceId(stateMachineInstance.getId());
if (stateInstance != null) {
exception.setStateName(stateInstance.getName());
exception.setStateInstanceId(stateInstance.getId());
}
}
return exception;
}
public static EngineExecutionException createEngineExecutionException(FrameworkErrorCode code, String message,
StateMachineInstance stateMachineInstance,
StateInstance stateInstance) {
return createEngineExecutionException(null, code, message, stateMachineInstance, stateInstance);
}
public static EngineExecutionException createEngineExecutionException(Exception e, FrameworkErrorCode code,
String message,
StateMachineInstance stateMachineInstance,
String stateName) {
EngineExecutionException exception = new EngineExecutionException(e, message, code);
if (stateMachineInstance != null) {
exception.setStateMachineName(stateMachineInstance.getStateMachine().getAppName());
exception.setStateMachineInstanceId(stateMachineInstance.getId());
exception.setStateName(stateName);
}
return exception;
}
/**
* getNetExceptionType
*
* @param throwable
* @return
*/
public static NetExceptionType getNetExceptionType(Throwable throwable) {
Throwable currentCause = throwable;
int dep = MAX_CAUSE_DEP;
while (currentCause != null && dep > 0) {
if (currentCause instanceof java.net.SocketTimeoutException) {
if (CONNECT_TIMED_OUT.equals(currentCause.getMessage())) {
return NetExceptionType.CONNECT_TIMEOUT_EXCEPTION;
} else {
return NetExceptionType.READ_TIMEOUT_EXCEPTION;
}
} else if (currentCause instanceof java.net.ConnectException) {
return NetExceptionType.CONNECT_EXCEPTION;
} else if (currentCause.getClass().getSimpleName().contains(CONNECT_TIME_OUT_EXCEPTION_CLASS_NAME)) {
return NetExceptionType.CONNECT_TIMEOUT_EXCEPTION;
} else if (currentCause.getClass().getSimpleName().contains(READ_TIME_OUT_EXCEPTION_CLASS_NAME)) {
return NetExceptionType.READ_TIMEOUT_EXCEPTION;
} else if (currentCause.getClass().getSimpleName().contains(CONNECT_EXCEPTION_CLASS_NAME)) {
return NetExceptionType.CONNECT_EXCEPTION;
} else {
Throwable parentCause = currentCause.getCause();
if (parentCause == null || parentCause == currentCause) {
break;
}
currentCause = parentCause;
dep--;
}
}
return NetExceptionType.NOT_NET_EXCEPTION;
}
public enum NetExceptionType {
/**
* Exception occurred while creating connection
*/
CONNECT_EXCEPTION,
/**
* create connection timeout
*/
CONNECT_TIMEOUT_EXCEPTION,
/**
* read timeout from remote(request has sent)
*/
READ_TIMEOUT_EXCEPTION,
/**
* not a network exception
*/
NOT_NET_EXCEPTION
}
/**
* Determine if the it is network exception
* @param throwable
* @return
*/
public static boolean isNetException(Throwable throwable) {
NetExceptionType netExceptionType = getNetExceptionType(throwable);
return netExceptionType != null && netExceptionType != NetExceptionType.NOT_NET_EXCEPTION;
}
}

View File

@@ -0,0 +1,121 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.utils;
import java.util.Map;
import io.seata.saga.engine.AsyncCallback;
import io.seata.saga.engine.StateMachineConfig;
import io.seata.saga.engine.StateMachineEngine;
import io.seata.saga.proctrl.Instruction;
import io.seata.saga.proctrl.ProcessContext;
import io.seata.saga.proctrl.ProcessType;
import io.seata.saga.proctrl.impl.ProcessContextImpl;
import io.seata.saga.statelang.domain.DomainConstants;
import io.seata.saga.statelang.domain.StateInstance;
import io.seata.saga.statelang.domain.StateMachineInstance;
/**
* Process Context Builder
*
* @author lorne.cl
*/
public class ProcessContextBuilder {
private ProcessContextImpl processContext;
private ProcessContextBuilder() {
this.processContext = new ProcessContextImpl();
}
public static ProcessContextBuilder create() {
return new ProcessContextBuilder();
}
public ProcessContext build() {
return processContext;
}
public ProcessContextBuilder withProcessType(ProcessType processType) {
if (processType != null) {
this.processContext.setVariable(ProcessContext.VAR_NAME_PROCESS_TYPE, processType);
}
return this;
}
public ProcessContextBuilder withAsyncCallback(AsyncCallback asyncCallback) {
if (asyncCallback != null) {
this.processContext.setVariable(DomainConstants.VAR_NAME_ASYNC_CALLBACK, asyncCallback);
}
return this;
}
public ProcessContextBuilder withInstruction(Instruction instruction) {
if (instruction != null) {
this.processContext.setInstruction(instruction);
}
return this;
}
public ProcessContextBuilder withStateMachineInstance(StateMachineInstance stateMachineInstance) {
if (stateMachineInstance != null) {
this.processContext.setVariable(DomainConstants.VAR_NAME_STATEMACHINE_INST, stateMachineInstance);
this.processContext.setVariable(DomainConstants.VAR_NAME_STATEMACHINE,
stateMachineInstance.getStateMachine());
}
return this;
}
public ProcessContextBuilder withStateMachineEngine(StateMachineEngine stateMachineEngine) {
if (stateMachineEngine != null) {
this.processContext.setVariable(DomainConstants.VAR_NAME_STATEMACHINE_ENGINE, stateMachineEngine);
}
return this;
}
public ProcessContextBuilder withStateMachineConfig(StateMachineConfig stateMachineConfig) {
if (stateMachineConfig != null) {
this.processContext.setVariable(DomainConstants.VAR_NAME_STATEMACHINE_CONFIG, stateMachineConfig);
}
return this;
}
public ProcessContextBuilder withStateMachineContextVariables(Map<String, Object> contextVariables) {
if (contextVariables != null) {
this.processContext.setVariable(DomainConstants.VAR_NAME_STATEMACHINE_CONTEXT, contextVariables);
}
return this;
}
public ProcessContextBuilder withOperationName(String operationName) {
if (operationName != null) {
this.processContext.setVariable(DomainConstants.VAR_NAME_OPERATION_NAME, operationName);
}
return this;
}
public ProcessContextBuilder withStateInstance(StateInstance stateInstance) {
if (stateInstance != null) {
this.processContext.setVariable(DomainConstants.VAR_NAME_STATE_INST, stateInstance);
}
return this;
}
public ProcessContextBuilder withIsAsyncExecution(boolean isAsyncExecution) {
this.processContext.setVariable(DomainConstants.VAR_NAME_IS_ASYNC_EXECUTION, isAsyncExecution);
return this;
}
}

View File

@@ -0,0 +1,3 @@
io.seata.saga.engine.pcext.interceptors.ServiceTaskHandlerInterceptor
io.seata.saga.engine.pcext.interceptors.ScriptTaskHandlerInterceptor
io.seata.saga.engine.pcext.interceptors.LoopTaskHandlerInterceptor

View File

@@ -0,0 +1 @@
io.seata.saga.engine.pcext.interceptors.EndStateRouterInterceptor

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 1999-2019 Seata.io Group.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>seata-saga</artifactId>
<groupId>io.seata</groupId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<name>seata-saga-processctrl ${project.version}</name>
<artifactId>seata-saga-processctrl</artifactId>
<packaging>jar</packaging>
</project>

View File

@@ -0,0 +1,77 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.proctrl;
import java.util.Map;
/**
* Hierarchical process context
*
* @author lorne.cl
*/
public interface HierarchicalProcessContext extends ProcessContext {
/**
* Gets get variable locally.
*
* @param name the name
* @return the get variable locally
*/
Object getVariableLocally(String name);
/**
* Sets set variable locally.
*
* @param name the name
* @param value the value
*/
void setVariableLocally(String name, Object value);
/**
* Gets get variables locally.
*
* @return the get variables locally
*/
Map<String, Object> getVariablesLocally();
/**
* Sets set variables locally.
*
* @param variables the variables
*/
void setVariablesLocally(Map<String, Object> variables);
/**
* Has variable local boolean.
*
* @param name the name
* @return the boolean
*/
boolean hasVariableLocal(String name);
/**
* Remove variable locally.
*
* @param name the name
* @return the removed variable or null
*/
Object removeVariableLocally(String name);
/**
* Clear locally.
*/
void clearLocally();
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.proctrl;
/**
* Instruction
*
* @author jin.xie
* @author xi.chen
* @author lorne.cl
*/
public interface Instruction {
}

View File

@@ -0,0 +1,98 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.proctrl;
import java.util.Map;
/**
* Process Context
*
* @author jin.xie
* @author lorne.cl
*/
public interface ProcessContext {
String VAR_NAME_PROCESS_TYPE = "_ProcessType_";
/**
* Gets get variable.
*
* @param name the name
* @return the get variable
*/
Object getVariable(String name);
/**
* Sets set variable.
*
* @param name the name
* @param value the value
*/
void setVariable(String name, Object value);
/**
* Gets get variables.
*
* @return the get variables
*/
Map<String, Object> getVariables();
/**
* Sets set variables.
*
* @param variables the variables
*/
void setVariables(Map<String, Object> variables);
/**
* Remove variable.
*
* @param name the name
* @return the removed variable or null
*/
Object removeVariable(String name);
/**
* Has variable boolean.
*
* @param name the name
* @return the boolean
*/
boolean hasVariable(String name);
/**
* Gets get instruction.
*
* @return the get instruction
*/
Instruction getInstruction();
/**
* Sets set instruction.
*
* @param instruction the instruction
*/
void setInstruction(Instruction instruction);
/**
* Gets get instruction.
*
* @param <T> the type parameter
* @param clazz the clazz
* @return the get instruction
*/
<T extends Instruction> T getInstruction(Class<T> clazz);
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.proctrl;
import io.seata.common.exception.FrameworkException;
/**
* Process Controller
*
* @author jin.xie
* @author lorne.cl
*/
public interface ProcessController {
/**
* process business logic
*
* @param context
* @throws FrameworkException
*/
void process(ProcessContext context) throws FrameworkException;
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.proctrl;
import io.seata.common.exception.FrameworkException;
/**
* Process Router
*
* @author jin.xie
* @author lorne.cl
*/
public interface ProcessRouter {
/**
* route
*
* @param context
* @return
* @throws FrameworkException
*/
Instruction route(ProcessContext context) throws FrameworkException;
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.proctrl;
/**
* Process type
*
* @author jin.xie
* @author lorne.cl
*/
public enum ProcessType {
/**
* SEATA State Language
*/
STATE_LANG("STATE_LANG", "SEATA State Language");
private String code;
private String message;
ProcessType(String code, String message) {
this.code = code;
this.message = message;
}
/**
* get enum by code
*
* @param code
* @return
*/
public static ProcessType getEnumByCode(String code) {
for (ProcessType codetmp : ProcessType.values()) {
if (codetmp.getCode().equalsIgnoreCase(code)) {
return codetmp;
}
}
return null;
}
/**
* get code
*
* @return
*/
public String getCode() {
return code;
}
/**
* get message
*
* @return
*/
public String getMessage() {
return message;
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.proctrl.eventing;
import java.util.List;
import io.seata.common.exception.FrameworkException;
/**
* Event bus
*
* @author lorne.cl
*/
public interface EventBus<E> {
/**
* insert add element into bus
*
* @param e
* @return
* @throws FrameworkException
*/
boolean offer(E e) throws FrameworkException;
/**
* get event consumers
*
* @param clazz
* @return
*/
List<EventConsumer> getEventConsumers(Class<?> clazz);
/**
* register event consumer
*
* @param eventConsumer
*/
void registerEventConsumer(EventConsumer eventConsumer);
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.proctrl.eventing;
import io.seata.common.exception.FrameworkException;
/**
* Event Consumer
*
* @author lorne.cl
*/
public interface EventConsumer<E> {
/**
* process
*
* @param event
* @throws FrameworkException
*/
void process(E event) throws FrameworkException;
/**
* if thd handler can handle this class return true otherwise return false
*
* @param clazz
* @return
*/
boolean accept(Class<E> clazz);
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.proctrl.eventing;
import io.seata.common.exception.FrameworkException;
/**
* Event publisher
*
* @author lorne.cl
*/
public interface EventPublisher<E> {
boolean publish(E event) throws FrameworkException;
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.proctrl.eventing.impl;
import java.util.ArrayList;
import java.util.List;
import io.seata.saga.proctrl.eventing.EventBus;
import io.seata.saga.proctrl.eventing.EventConsumer;
/**
* Abstract Event Bus
*
* @author jin.xie
* @author lorne.cl
*/
public abstract class AbstractEventBus<E> implements EventBus<E> {
private List<EventConsumer> eventConsumerList = new ArrayList<>();
@Override
public List<EventConsumer> getEventConsumers(Class clazz) {
List<EventConsumer> acceptedConsumers = new ArrayList<>();
for (EventConsumer eventConsumer : eventConsumerList) {
if (eventConsumer.accept(clazz)) {
acceptedConsumers.add(eventConsumer);
}
}
return acceptedConsumers;
}
@Override
public void registerEventConsumer(EventConsumer eventConsumer) {
eventConsumerList.add(eventConsumer);
}
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.proctrl.eventing.impl;
import java.util.List;
import java.util.concurrent.ThreadPoolExecutor;
import io.seata.common.exception.FrameworkException;
import io.seata.common.util.CollectionUtils;
import io.seata.saga.proctrl.ProcessContext;
import io.seata.saga.proctrl.eventing.EventConsumer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Asynchronized EventBus
*
* @author lorne.cl
*/
public class AsyncEventBus extends AbstractEventBus<ProcessContext> {
private static final Logger LOGGER = LoggerFactory.getLogger(AsyncEventBus.class);
private ThreadPoolExecutor threadPoolExecutor;
@Override
public boolean offer(ProcessContext context) throws FrameworkException {
List<EventConsumer> eventConsumers = getEventConsumers(context.getClass());
if (CollectionUtils.isEmpty(eventConsumers)) {
if (LOGGER.isWarnEnabled()) {
LOGGER.warn("cannot find event handler by class: " + context.getClass());
}
return false;
}
for (EventConsumer eventConsumer : eventConsumers) {
threadPoolExecutor.execute(() -> eventConsumer.process(context));
}
return true;
}
public void setThreadPoolExecutor(ThreadPoolExecutor threadPoolExecutor) {
this.threadPoolExecutor = threadPoolExecutor;
}
}

View File

@@ -0,0 +1,78 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.proctrl.eventing.impl;
import java.util.List;
import java.util.Stack;
import io.seata.common.exception.FrameworkException;
import io.seata.common.util.CollectionUtils;
import io.seata.saga.proctrl.ProcessContext;
import io.seata.saga.proctrl.eventing.EventConsumer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Deliver event to event consumer directly
*
* @author lorne.cl
*/
public class DirectEventBus extends AbstractEventBus<ProcessContext> {
private static final Logger LOGGER = LoggerFactory.getLogger(DirectEventBus.class);
private static final String VAR_NAME_SYNC_EXE_STACK = "_sync_execution_stack_";
@Override
public boolean offer(ProcessContext context) throws FrameworkException {
List<EventConsumer> eventHandlers = getEventConsumers(context.getClass());
if (CollectionUtils.isEmpty(eventHandlers)) {
if (LOGGER.isWarnEnabled()) {
LOGGER.warn("cannot find event handler by class: " + context.getClass());
}
return false;
}
boolean isFirstEvent = false;
Stack<ProcessContext> currentStack = (Stack<ProcessContext>)context.getVariable(VAR_NAME_SYNC_EXE_STACK);
if (currentStack == null) {
synchronized (context) {
currentStack = (Stack<ProcessContext>)context.getVariable(VAR_NAME_SYNC_EXE_STACK);
if (currentStack == null) {
currentStack = new Stack<>();
context.setVariable(VAR_NAME_SYNC_EXE_STACK, currentStack);
isFirstEvent = true;
}
}
}
currentStack.push(context);
if (isFirstEvent) {
try {
while (currentStack.size() > 0) {
ProcessContext currentContext = currentStack.pop();
for (EventConsumer eventHandler : eventHandlers) {
eventHandler.process(currentContext);
}
}
} finally {
context.removeVariable(VAR_NAME_SYNC_EXE_STACK);
}
}
return true;
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.proctrl.eventing.impl;
import io.seata.common.exception.FrameworkException;
import io.seata.saga.proctrl.ProcessContext;
import io.seata.saga.proctrl.ProcessController;
import io.seata.saga.proctrl.eventing.EventConsumer;
/**
* ProcessCtrl Event Consumer
*
* @author lorne.cl
*/
public class ProcessCtrlEventConsumer implements EventConsumer<ProcessContext> {
private ProcessController processController;
@Override
public void process(ProcessContext event) throws FrameworkException {
processController.process(event);
}
@Override
public boolean accept(Class<ProcessContext> clazz) {
return ProcessContext.class.isAssignableFrom(clazz);
}
public void setProcessController(ProcessController processController) {
this.processController = processController;
}
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.proctrl.eventing.impl;
import io.seata.common.exception.FrameworkException;
import io.seata.saga.proctrl.ProcessContext;
import io.seata.saga.proctrl.eventing.EventBus;
import io.seata.saga.proctrl.eventing.EventPublisher;
/**
* ProcessCtrl Event Pulisher
*
* @author lorne.cl
*/
public class ProcessCtrlEventPublisher implements EventPublisher<ProcessContext> {
private EventBus<ProcessContext> eventBus;
@Override
public boolean publish(ProcessContext event) throws FrameworkException {
return eventBus.offer(event);
}
public void setEventBus(EventBus<ProcessContext> eventBus) {
this.eventBus = eventBus;
}
}

View File

@@ -0,0 +1,91 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.proctrl.handler;
import java.util.Map;
import io.seata.common.exception.FrameworkErrorCode;
import io.seata.common.exception.FrameworkException;
import io.seata.saga.proctrl.Instruction;
import io.seata.saga.proctrl.ProcessContext;
import io.seata.saga.proctrl.ProcessRouter;
import io.seata.saga.proctrl.ProcessType;
import io.seata.saga.proctrl.eventing.EventPublisher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Default Router handler
*
* @author jin.xie
* @author lorne.cl
*/
public class DefaultRouterHandler implements RouterHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultRouterHandler.class);
private EventPublisher<ProcessContext> eventPublisher;
private Map<String, ProcessRouter> processRouters;
public static ProcessType matchProcessType(ProcessContext context) {
ProcessType processType = (ProcessType)context.getVariable(ProcessContext.VAR_NAME_PROCESS_TYPE);
if (processType == null) {
processType = ProcessType.STATE_LANG;
}
return processType;
}
@Override
public void route(ProcessContext context) throws FrameworkException {
try {
ProcessType processType = matchProcessType(context);
if (processType == null) {
if (LOGGER.isWarnEnabled()) {
LOGGER.warn("Process type not found, context= {}", context);
}
throw new FrameworkException(FrameworkErrorCode.ProcessTypeNotFound);
}
ProcessRouter processRouter = processRouters.get(processType.getCode());
if (processRouter == null) {
LOGGER.error("Cannot find process router by type {}, context = {}", processType.getCode(), context);
throw new FrameworkException(FrameworkErrorCode.ProcessRouterNotFound);
}
Instruction instruction = processRouter.route(context);
if (instruction == null) {
LOGGER.info("route instruction is null, process end");
} else {
context.setInstruction(instruction);
eventPublisher.publish(context);
}
} catch (FrameworkException e) {
throw e;
} catch (Exception ex) {
throw new FrameworkException(ex, ex.getMessage(), FrameworkErrorCode.UnknownAppError);
}
}
public void setEventPublisher(EventPublisher<ProcessContext> eventPublisher) {
this.eventPublisher = eventPublisher;
}
public void setProcessRouters(Map<String, ProcessRouter> processRouters) {
this.processRouters = processRouters;
}
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.proctrl.handler;
import io.seata.common.exception.FrameworkException;
import io.seata.saga.proctrl.ProcessContext;
/**
* Process Handler
*
* @author jin.xie
* @author lorne.cl
*/
public interface ProcessHandler {
/**
* process
*
* @param context
* @throws FrameworkException
*/
void process(ProcessContext context) throws FrameworkException;
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.proctrl.handler;
import io.seata.common.exception.FrameworkException;
import io.seata.saga.proctrl.ProcessContext;
/**
* Router Handler
*
* @author jin.xie
* @author lorne.cl
*/
public interface RouterHandler {
/**
* route
*
* @param context
* @throws FrameworkException
*/
void route(ProcessContext context) throws FrameworkException;
}

View File

@@ -0,0 +1,168 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.proctrl.impl;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import io.seata.saga.proctrl.HierarchicalProcessContext;
import io.seata.saga.proctrl.Instruction;
import io.seata.saga.proctrl.ProcessContext;
/**
* The default process context implementation
*
* @author lorne.cl
*/
public class ProcessContextImpl implements HierarchicalProcessContext, ProcessContext {
private Map<String, Object> variables = new ConcurrentHashMap<>();
private Instruction instruction;
private ProcessContext parent;
@Override
public Object getVariable(String name) {
if (variables.containsKey(name)) {
return variables.get(name);
}
if (parent != null) {
return parent.getVariable(name);
}
return null;
}
@Override
public void setVariable(String name, Object value) {
if (variables.containsKey(name)) {
setVariableLocally(name, value);
} else {
if (parent != null) {
parent.setVariable(name, value);
} else {
setVariableLocally(name, value);
}
}
}
@Override
public Map<String, Object> getVariables() {
final Map<String, Object> collectedVariables = new HashMap<>();
if (parent != null) {
collectedVariables.putAll(parent.getVariables());
}
variables.forEach(collectedVariables::put);
return collectedVariables;
}
@Override
public void setVariables(final Map<String, Object> variables) {
if (variables != null) {
variables.forEach(this::setVariable);
}
}
@Override
public Object getVariableLocally(String name) {
return variables.get(name);
}
@Override
public void setVariableLocally(String name, Object value) {
variables.put(name, value);
}
@Override
public Map<String, Object> getVariablesLocally() {
return Collections.unmodifiableMap(variables);
}
@Override
public void setVariablesLocally(Map<String, Object> variables) {
this.variables.putAll(variables);
}
@Override
public boolean hasVariable(String name) {
if (variables.containsKey(name)) {
return true;
}
if (parent != null) {
return parent.hasVariable(name);
}
return false;
}
@Override
public Instruction getInstruction() {
return instruction;
}
@Override
public void setInstruction(Instruction instruction) {
this.instruction = instruction;
}
@Override
public <T extends Instruction> T getInstruction(Class<T> clazz) {
return (T)instruction;
}
@Override
public boolean hasVariableLocal(String name) {
return variables.containsKey(name);
}
@Override
public Object removeVariable(String name) {
if (variables.containsKey(name)) {
return variables.remove(name);
}
if (parent != null) {
return parent.removeVariable(name);
}
return null;
}
@Override
public Object removeVariableLocally(String name) {
return variables.remove(name);
}
@Override
public void clearLocally() {
variables.clear();
}
public ProcessContext getParent() {
return parent;
}
public void setParent(ProcessContext parent) {
this.parent = parent;
}
@Override
public String toString() {
return "{" + "variables=" + variables + ", instruction=" + instruction + ", parent=" + parent + '}';
}
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.proctrl.impl;
import io.seata.common.exception.FrameworkErrorCode;
import io.seata.common.exception.FrameworkException;
import io.seata.saga.proctrl.ProcessContext;
import io.seata.saga.proctrl.ProcessController;
import io.seata.saga.proctrl.process.BusinessProcessor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Default implementation of Process controller
*
* @author jin.xie
* @author lorne.cl
*/
public class ProcessControllerImpl implements ProcessController {
private static final Logger LOGGER = LoggerFactory.getLogger(ProcessControllerImpl.class);
private BusinessProcessor businessProcessor;
@Override
public void process(ProcessContext context) throws FrameworkException {
try {
businessProcessor.process(context);
businessProcessor.route(context);
} catch (FrameworkException fex) {
throw fex;
} catch (Exception ex) {
LOGGER.error("Unknown exception occurred, context = {}", context, ex);
throw new FrameworkException(ex, "Unknown exception occurred", FrameworkErrorCode.UnknownAppError);
}
}
public void setBusinessProcessor(BusinessProcessor businessProcessor) {
this.businessProcessor = businessProcessor;
}
}

Some files were not shown because too many files have changed in this diff Show More