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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,71 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.saga.engine.repo;
import java.io.IOException;
import io.seata.saga.statelang.domain.StateMachine;
import org.springframework.core.io.Resource;
/**
* StateMachineRepository
*
* @author lorne.cl
*/
public interface StateMachineRepository {
/**
* Gets get state machine by id.
*
* @param stateMachineId the state machine id
* @return the get state machine by id
*/
StateMachine getStateMachineById(String stateMachineId);
/**
* Gets get state machine.
*
* @param stateMachineName the state machine name
* @param tenantId the tenant id
* @return the get state machine
*/
StateMachine getStateMachine(String stateMachineName, String tenantId);
/**
* Gets get state machine.
*
* @param stateMachineName the state machine name
* @param tenantId the tenant id
* @param version the version
* @return the get state machine
*/
StateMachine getStateMachine(String stateMachineName, String tenantId, String version);
/**
* Register the state machine to the repository (if the same version already exists, return the existing version)
*
* @param stateMachine
*/
StateMachine registryStateMachine(StateMachine stateMachine);
/**
* registry by resources
*
* @param resources
* @param tenantId
*/
void registryByResources(Resource[] resources, String tenantId) throws IOException;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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