简体   繁体   中英

Start Spring State Machine with HTTP call

I want to trigger the Spring State Machine model loaded from Papyrus with an HTTP POST. If I send let's say {"state_machine":"START"} over HTTP I would like to start the predefined state machine model. Is this possible? Or looking from different view, is it possible to create and run REST API inside the Spring State Machine model?

I'm using Spring Boot with Spring State Machine dependency. There is defined endpoint on Swagger to send HTTP POST to Service class.

It is based on this project where you can check all the code for State Machine/s because I basically just want to integrate HTTP call to it.

I'm getting NullPointerException considering Spring Statemachine at Service class for line 65 which is stateMachineOne = this.stateMachineFactory.getStateMachine("machineone"); . Here is full exception:

java.lang.NullPointerException
        at test.umlspringstatemachine.SSMService.RunSSM(SSMService.java:65)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base/java.lang.reflect.Method.invoke(Method.java:566)
        at org.apache.camel.support.ObjectHelper.invokeMethodSafe(ObjectHelper.java:372)
        at org.apache.camel.component.bean.MethodInfo.invoke(MethodInfo.java:494)
        at org.apache.camel.component.bean.MethodInfo$1.doProceed(MethodInfo.java:316)
        at org.apache.camel.component.bean.MethodInfo$1.proceed(MethodInfo.java:286)
        at org.apache.camel.component.bean.AbstractBeanProcessor.process(AbstractBeanProcessor.java:146)
        at org.apache.camel.component.bean.BeanProcessor.process(BeanProcessor.java:81)
        at org.apache.camel.processor.errorhandler.RedeliveryErrorHandler$RedeliveryTask.doRun(RedeliveryErrorHandler.java:780)
        at org.apache.camel.processor.errorhandler.RedeliveryErrorHandler$RedeliveryTask.run(RedeliveryErrorHandler.java:688)
        at org.apache.camel.impl.engine.DefaultReactiveExecutor$Worker.schedule(DefaultReactiveExecutor.java:181)
        at org.apache.camel.impl.engine.DefaultReactiveExecutor.scheduleMain(DefaultReactiveExecutor.java:62)
        at org.apache.camel.processor.Pipeline.process(Pipeline.java:167)
        at org.apache.camel.impl.engine.CamelInternalProcessor.process(CamelInternalProcessor.java:388)
        at org.apache.camel.impl.engine.DefaultAsyncProcessorAwaitManager.process(DefaultAsyncProcessorAwaitManager.java:83)
        at org.apache.camel.support.AsyncProcessorSupport.process(AsyncProcessorSupport.java:41)
        at org.apache.camel.http.common.CamelServlet.doExecute(CamelServlet.java:319)
        at org.apache.camel.http.common.CamelServlet.doService(CamelServlet.java:214)
        at org.apache.camel.http.common.CamelServlet.service(CamelServlet.java:130)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:741)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:93)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)
        at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
        at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541)
        at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)
        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
        at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:373)
        at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
        at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868)
        at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1590)
        at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
        at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
        at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.base/java.lang.Thread.run(Thread.java:829)

Change that I made from that project is that I removed next part of code from UmlSpringStateMachineApplication.java so the State Machine doesn't start when I start the whole app:

@Override
    public void run(String... args) throws Exception {

        synchronized (stateMachineOne) {
             stateMachineOne.getExtendedState().getVariables().put("foo", "machine1");
             stateMachineOne.start();
        }
       
        synchronized (stateMachineTwo) {
            if(stateMachineOne.isComplete()) {
                stateMachineTwo.getExtendedState().getVariables().put("foo", (String)stateMachineOne.getExtendedState().getVariables().get("foo"));
                stateMachineTwo.start();
            }
        }
        
    }

Inside the Service class I have something like this below, but I get null exception like the Spring State Machine context or something is missing. I would like to stateMachineOne.start(); once the START from HTTP POST is received.

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.camel.Exchange;
import org.apache.camel.Message;
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.config.StateMachineFactory;

import java.util.LinkedList;
import java.util.List;
import java.util.Map;

public class SSMService {

    @Autowired
    private StateMachineFactory<String, String> stateMachineFactory;

    @Autowired
    private StateMachine<String, String> stateMachineOne;

    public SSMService() {
    }

    public void TriggerSSM(Exchange exchange) {

        Message camelMessage = exchange.getIn();

        ObjectMapper mapObject = new ObjectMapper();
        Map<String, Object> mapObj = mapObject.convertValue(camelMessage.getBody(), Map.class);

        try {
            JSONObject json = new JSONObject(mapObj);

            String state_machine= json.getString("state_machine");

                if (state_machine.equals("START")) {

                    stateMachineOne = this.stateMachineFactory.getStateMachine("machineone");
                    stateMachineOne.getExtendedState().getVariables().put("foo", "machine1");
                    stateMachineOne.start();

                }
            }

        } catch (JsonProcessingException | JSONException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

}

This is the config class:

import ch.qos.logback.classic.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.statemachine.config.EnableStateMachine;
import org.springframework.statemachine.config.StateMachineConfigurerAdapter;
import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer;
import org.springframework.statemachine.config.builders.StateMachineModelConfigurer;
import org.springframework.statemachine.config.model.StateMachineModelFactory;
import org.springframework.statemachine.listener.StateMachineListener;
import org.springframework.statemachine.listener.StateMachineListenerAdapter;
import org.springframework.statemachine.state.State;
import org.springframework.statemachine.uml.UmlStateMachineModelFactory;
import org.springframework.statemachine.config.EnableStateMachineFactory;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Configuration
//@EnableStateMachineFactory
@EnableStateMachine(name="stateMachineOne")
public class StateMachineOneConfig extends StateMachineConfigurerAdapter<String, String> {

    Logger log;
    
    @Override
    public void configure(StateMachineConfigurationConfigurer<String, String> config) throws Exception {
        config.withConfiguration().autoStartup(false).listener(listener1()).machineId("machineone");
    }

    @Override
    public void configure(StateMachineModelConfigurer<String, String> model) throws Exception {
        model.withModel().factory(modelFactory1());
    }

    @Bean
    public StateMachineModelFactory<String, String> modelFactory1() {
        return new UmlStateMachineModelFactory("classpath:papyrus/StateMachine1.uml");
    }
    
    @Bean
    public StateMachineListener<String, String> listener1() {
        return new StateMachineListenerAdapter<String, String>() {
            @Override
            public void stateChanged(State<String, String> from, State<String, String> to) {
                log.info("State 1 changed to " + to.getId());
            }
        };

    }

}

This is pom.xml file with dependencies:

<?xml version="1.0" encoding="UTF-8"?>
<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/maven-v4_0_0.xsd">

    <modelVersion>4.0.0</modelVersion>
    <groupId>test</groupId>
    <artifactId>camel</artifactId>
    <packaging>jar</packaging>
    <version>1.0</version>
    <name>Camel - OpenAPI - Spring State Machine</name>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring.boot-version>2.3.0.RELEASE</spring.boot-version>
        <jackson.swagger.version>2.12.0</jackson.swagger.version>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <start-class>test.umlspringstatemachine.UmlSpringStateMachineApplication</start-class>
    </properties>

    <dependencyManagement>
        <dependencies>
            <!-- Spring Boot BOM -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring.boot-version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!-- Camel BOM -->
            <dependency>
                <groupId>org.apache.camel.springboot</groupId>
                <artifactId>camel-spring-boot-dependencies</artifactId>
                <version>3.9.0</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.springframework.statemachine</groupId>
            <artifactId>spring-statemachine-core</artifactId>
            <version>2.0.0.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.statemachine</groupId>
            <artifactId>spring-statemachine-uml</artifactId>
            <version>2.0.0.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.statemachine</groupId>
            <artifactId>spring-statemachine-autoconfigure</artifactId>
            <version>2.0.0.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.statemachine</groupId>
            <artifactId>spring-statemachine-data-jpa</artifactId>
            <version>2.0.0.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.20</version>
        </dependency>
        <!-- Spring Boot -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!-- Camel -->
        <dependency>
            <groupId>org.apache.camel.springboot</groupId>
            <artifactId>camel-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.camel.springboot</groupId>
            <artifactId>camel-stream-starter</artifactId>
        </dependency>
        <dependency>
         <groupId>org.apache.camel.springboot</groupId>
         <artifactId>camel-jackson-starter</artifactId>
        </dependency>
        <!-- REST -->
        <dependency>
            <groupId>org.apache.camel.springboot</groupId>
            <artifactId>camel-rest-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.camel.springboot</groupId>
            <artifactId>camel-servlet-starter</artifactId>
        </dependency>
        <!-- Swagger -->
        <dependency>
            <groupId>org.apache.camel.springboot</groupId>
            <artifactId>camel-swagger-java-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.camel.springboot</groupId>
            <artifactId>camel-rest-swagger-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.camel</groupId>
            <artifactId>camel-swagger-java</artifactId>
            <version>3.9.0</version>
        </dependency>
        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>swagger-ui</artifactId>
            <version>3.51.2</version>
        </dependency>
        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>webjars-locator</artifactId>
            <version>0.42</version>
        </dependency>
        <!-- Kafka -->
        <dependency>
            <groupId>org.apache.camel</groupId>
            <artifactId>camel-kafka</artifactId>
            <version>3.9.0</version>
        </dependency>
        <!-- JSON -->
        <dependency>
            <groupId>org.codehaus.jettison</groupId>
            <artifactId>jettison</artifactId>
            <version>1.4.1</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.jacoco</groupId>
                <artifactId>jacoco-maven-plugin</artifactId>
                <version>0.8.0</version>
                <executions>
                    <execution>
                        <id>default-prepare-agent</id>
                        <goals>
                            <goal>prepare-agent</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>default-report</id>
                        <phase>prepare-package</phase>
                        <goals>
                            <goal>report</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <!-- OWASP Dependency Check - https://www.owasp.org/index.php/OWASP_Dependency_Check -->
            <plugin>
                <groupId>org.owasp</groupId>
                <artifactId>dependency-check-maven</artifactId>
                <version>3.0.2</version>
                <configuration>
                    <format>XML</format>
                    <outputDirectory>${dependency.check.report.dir}</outputDirectory>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

I cloned your project and made several changes to have it working, the main ones can be listed here:

1- Added component scan to the main class to be able to find state machine configuration beans (including package rename). Got rid of SSM custom service and change to use your custom implementation

@SpringBootApplication
@ComponentScan(basePackages = {"uml.statemachine.*"})
public class UmlSpringStateMachineApplication implements CommandLineRunner 


@Autowired
    private SSMService stateMachineService;

2- boilerplate logic to aquire machines according to the type you want

@Component
public class SSMService {

    @Autowired
    @Qualifier(value="config_one")
    private StateMachineFactory<String, String> stateMachineFactoryOne;

    @Autowired
    @Qualifier(value="config_two")
    private StateMachineFactory<String, String> stateMachineFactoryTwo;

    public SSMService() {
    }

    public StateMachine<String, String> acquireMachine(String id, String version){
        if (version.equals("one")){
            return stateMachineFactoryOne.getStateMachine(id);
        }
        else{
            return stateMachineFactoryTwo.getStateMachine(id);
        }
    }

3- Commented out StateMachineServiceConfig due to injection issues (you can look further)

4- Replaced for the factory annotation in your config classes

@Configuration
@EnableStateMachineFactory(name = "config_one")
public class StateMachineOneConfig
        extends StateMachineConfigurerAdapter<String, String> {

5- In my case, the project was not compiling so I had to include few extra dependencies to your pom (might not be necessary)

<repositories>
        <repository>
            <id>spring-snapshots</id>
            <url>http://repo.spring.io/snapshot</url>
            <snapshots><enabled>true</enabled></snapshots>
        </repository>
        <repository>
            <id>spring-milestones</id>
            <url>http://repo.spring.io/milestone</url>
        </repository>
    </repositories>
    <pluginRepositories>
        <pluginRepository>
            <id>spring-snapshots</id>
            <url>http://repo.spring.io/snapshot</url>
        </pluginRepository>
        <pluginRepository>
            <id>spring-milestones</id>
            <url>http://repo.spring.io/milestone</url>
        </pluginRepository>
    </pluginRepositories>

You can find the list of changes in the repository I created to help you

https://github.com/Daanielvb/state-machine-papyrus-help

Hope that's helpful

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM