chore(project): 添加项目配置文件和忽略规则
- 添加 Babel 配置文件支持 ES6+ 语法转换 - 添加 ESLint 忽略规则和配置文件 - 添加 Git 忽略规则文件 - 添加 Travis CI 配置文件 - 添加 1.4.2 版本变更日志文件 - 添加 Helm 图表辅助模板文件 - 添加 Helm 忽略规则文件
This commit is contained in:
64
saga/pom.xml
Normal file
64
saga/pom.xml
Normal 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>
|
||||
52
saga/seata-saga-engine-store/pom.xml
Normal file
52
saga/seata-saga-engine-store/pom.xml
Normal 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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
io.seata.saga.engine.pcext.interceptors.InSagaBranchHandlerInterceptor
|
||||
55
saga/seata-saga-engine/pom.xml
Normal file
55
saga/seata-saga-engine/pom.xml
Normal 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>
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
io.seata.saga.engine.pcext.interceptors.EndStateRouterInterceptor
|
||||
31
saga/seata-saga-processctrl/pom.xml
Normal file
31
saga/seata-saga-processctrl/pom.xml
Normal 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>
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 + '}';
|
||||
}
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user