Spring State Machine does not maintain order in which actions are triggered when configuration is read from DB - spring-statemachine

StateMachineFactory is configured to read configuration from db repository. However then statemachine is created, and events are send to machine, the order of actions executed is not maintained. I suspect, it's because the results returned from DB are not in any particular order.
Following is a POC for the same. Logs are also attached
#Configuration
#EnableStateMachineFactory
public class Config extends StateMachineConfigurerAdapter<String, String> {
#Autowired
private StateRepository<? extends RepositoryState> stateRepository;
#Autowired
private TransitionRepository<? extends RepositoryTransition> transitionRepository;
#Override
public void configure(StateMachineModelConfigurer<String, String> model) throws Exception {
model
.withModel()
.factory(modelFactory());
}
#Bean
public StateMachineModelFactory<String, String> modelFactory() {
return new RepositoryStateMachineModelFactory(stateRepository, transitionRepository);
}
#Bean
public Action<String, String> action1() {
return context -> System.out.println("Action1");
}
#Bean
public Action<String, String> action2() {
return context -> System.out.println("Action2");
}
}
#SpringBootApplication
public class Main implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
#Autowired
StateMachineFactory<String, String> factory;
#Autowired
ApplicationContext context;
#Override
public void run(String... args) throws Exception {
JpaStateRepository stateRepository = context.getBean(JpaStateRepository.class);
JpaTransitionRepository transitionRepository = context.getBean(JpaTransitionRepository.class);
JpaActionRepository actionRepository = context.getBean(JpaActionRepository.class);
JpaRepositoryState stateS1 = new JpaRepositoryState("UNPAID", true);
JpaRepositoryState stateS2 = new JpaRepositoryState("DONE");
stateRepository.save(stateS1);
stateRepository.save(stateS2);
var action1 = new JpaRepositoryAction();
action1.setName("action1");
var action2 = new JpaRepositoryAction();
action2.setName("action2");
actionRepository.save(action1);
actionRepository.save(action2);
JpaRepositoryTransition transitionS1ToS2 = new JpaRepositoryTransition(null, stateS1, stateS1, "TEST", Set.of(action1, action2));
transitionRepository.save(transitionS1ToS2);
for (int i = 0; i < 10; i++) {
var sm = factory.getStateMachine();
sm.startReactively().block();
sm.sendEvent("TEST");
}
}
}
Logs:
2022-10-12 22:01:19.474 INFO 58704 --- [ restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 7082 (http) with context path ''
2022-10-12 22:01:19.482 INFO 58704 --- [ restartedMain] com.poc.Main : Started Main in 2.076 seconds (JVM running for 2.288)
Action2
Action1
Action1
Action2
Is there any way to achieve order when done configuration is read from DB?

Related

Can not execute controller test with #SpringBootTest

I have a Spring Boot application. Version is 2.3.1.
Main Application looks like:
#AllArgsConstructor
#SpringBootApplication
public class LocalServiceApplication implements CommandLineRunner {
private final DataService dataService;
private final QrReaderServer qrReaderServer;
private final MonitoringService monitoringService;
#Override
public void run(String... args) {
dataService.fetchData();
monitoringService.launchMonitoring();
qrReaderServer.launchServer();
}
public static void main(String[] args) {
SpringApplication.run(LocalServiceApplication.class, args);
}
}
After the application is started I have to execute 3 distinct steps which have done with CommandLineRunner:
first gets remote data and store it locally (for test profile this step is skipped)
start async folder monitoring for file uploads with WatchService.
launch TCP server
I have a controller like:
#Slf4j
#RestController
#AllArgsConstructor
#RequestMapping("/v1/permissions")
public class CarParkController {
private final PermissionService permissionService;
#PostMapping
public CarParkPermission createPermission(#RequestBody #Valid CarParkPermission permission) {
return permissionService.createPermission(permission);
}
}
Ant test with Junit 5 looks like:
#ActiveProfiles("test")
#AutoConfigureMockMvc
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
class CarParkControllerIntegrationTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private PermissionService permissionService;
private final Gson gson = new Gson();
#Test
void testCreatingNewPermissionSuccess() throws Exception {
CarParkPermission permission = CarParkPermission.builder()
.id(56)
.permissionCode("1234")
.build();
when(permissionService.createPermission(refEq(permission))).thenReturn(permission);
postPermission(permission).andExpect(status().isOk());
}
private <T> ResultActions postPermission(T instance) throws Exception {
return this.mockMvc.perform(post("/v1/permissions")
.contentType(MediaType.APPLICATION_JSON)
.content(gson.toJson(instance)));
}
}
Looks like it should work fine.
However, the test isn't executed:
2020-08-27 14:42:30.308 INFO 21800 --- [ main] c.s.i.CarParkControllerIntegrationTest : Started CarParkControllerIntegrationTest in 8.593 seconds (JVM running for 10.03)
2020-08-27 14:42:30.334 INFO 21800 --- [ main] c.s.s.s.DataServiceTestImpl : Fetch data for test profile is skipped
2020-08-27 14:42:30.336 DEBUG 21800 --- [ carpark-ex-1] c.s.monitoring.MonitoringServiceImpl : START_MONITORING Results from Cameras for folder: D:\results-from-camera
2020-08-27 14:42:30.751 DEBUG 21800 --- [ main] c.s.netty.TCPServer : TCP Server is STARTED : port 9090
After those lines execution hangs up forever.
UPDATE
Here are details for monitoring task:
#Async
#Override
public void launchMonitoring() {
log.debug("START_MONITORING Results from Cameras for folder: {}", properties.getFolder());
try {
WatchKey key;
while ((key = watchService.take()) != null) {
for (WatchEvent<?> event : key.pollEvents()) {
WatchEvent.Kind<?> kind = event.kind();
if (kind == ENTRY_CREATE) {
log.info("FILE_CREATED: {}", event.context());
// processing resource
deleteResource(zipFullPath);
} else if (kind == ENTRY_DELETE) {
log.info("RESOURCE_DELETED: {}", event.context());
}
}
key.reset();
}
} catch (InterruptedException e) {
log.error("interrupted exception for monitoring service", e);
Thread.currentThread().interrupt();
}
}
Also AsyncConfiguration is configured with TaskExecutor.
Launch method from TCPServer looks:
#Override
public void launchServer() {
try {
ChannelFuture serverChannelFuture = serverBootstrap.bind(hostAddress).sync();
log.debug("TCP Server is STARTED : port {}", hostAddress.getPort());
serverChannel = serverChannelFuture.channel().closeFuture().sync().channel();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
shutdownQuietly();
}
}
How to solve this issue?
Have understood that execution is blocked (thanks to M.Deinum).
So changed the last method for:
#Async
#Override
public void launchServer() {
// ...
}
And shifted to ObjectMapper instead of Gson for converting instance to JSON format:
#SpringBootTest
#AutoConfigureMockMvc
#ActiveProfiles("test")
class CarParkControllerIntegrationTest {
#Autowired
private MockMvc mockMvc;
#Autowired
private ObjectMapper mapper;
#Test
void testCreatingNewPermissionSuccess() throws Exception {
CarParkPermission permission = CarParkPermission.builder()
.id(444)
.permissionCode("1234")
.build();
postPermission(permission).andExpect(status().isOk());
}
private <T> ResultActions postPermission(T instance) throws Exception {
return this.mockMvc.perform(post("/v1/permissions")
.contentType(MediaType.APPLICATION_JSON)
.content(mapper.writeValueAsString(instance)));
}
}
And finally, it works fine.

Spring-Boot Failing to #Schedule tasks for List of beans created by #Configuration class

I am attempting to create List of beans of the same type in a class annotated by #Configuration. What I need is to execute #Scheduled function declared in those beans.
#SpringBootApplication
#EnableScheduling
public class DemoApplication
{
public static void main(String[] args)
{
SpringApplication.run(DemoApplication.class, args);
}
}
#Configuration
public class Config
{
#Bean
public List<Monitoring> mon()
{
List<Monitoring> list = new ArrayList<>();
for (int x = 0; x < 5; ++x)
{
list.add(new First());
}
return list;
}
}
public class First implements Monitoring
{
private static final Logger logger = LoggerFactory.getLogger(First.class);
#Override
public void doSth()
{
logger.info("first monitoring bean");
}
#Scheduled(fixedRate = 50)
private void init()
{
logger.info("scheduled task");
}
}
What am I expecting from these code snippets is for my 5 Beans of Monitoring to print "scheduled task" every 50ms, but I never see this output.
You are only creating a list as a bean with unmanaged Monitoring instances. That won't work.
You need to properly create the Monitoring beans (so that Spring can do its magic):
#Configuration
public class Config {
#Bean
public Monitoring first() {
return new First();
}
#Bean
public Monitoring second() {
return new First();
}
}

PublishSubscribeChannel using TaskExecutor - Thread behaviour

I have a simple spring dsl flow as follows:
#Configuration
public class OrderFlow {
private static final Logger logger = LoggerFactory.getLogger(OrderFlow.class);
#Autowired
private OrderSubFlow orderSubFlow;
#Autowired
private ThreadPoolTaskExecutor threadPoolTaskExecutor;
#Bean
public IntegrationFlow orders() {
return IntegrationFlows.from(MessageChannels.direct("order_input").get()).handle(new GenericHandler<Order>() {
#Override
public Object handle(Order order, Map<String, Object> headers) {
logger.info("Pre-Processing order with id: {}", order.getId());
return MessageBuilder.withPayload(order).copyHeaders(headers).build();
}
}).publishSubscribeChannel(threadPoolTaskExecutor, new Consumer<PublishSubscribeSpec>() {
#Override
public void accept(PublishSubscribeSpec t) {
t.subscribe(orderSubFlow);
}
}).handle(new GenericHandler<Order>() {
#Override
public Object handle(Order order, Map<String, Object> headers) {
logger.info("Post-Processing order with id: {}", order.getId());
return MessageBuilder.withPayload(order).copyHeaders(headers).build();
}
}).get();
}
#Bean
public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
threadPoolTaskExecutor.setMaxPoolSize(2);
threadPoolTaskExecutor.setCorePoolSize(2);
threadPoolTaskExecutor.setQueueCapacity(10);
return threadPoolTaskExecutor;
}
}
And the OrderSubFlow is
#Configuration
public class OrderSubFlow implements IntegrationFlow {
private static final Logger logger = LoggerFactory.getLogger(OrderSubFlow.class);
#Override
public void configure(IntegrationFlowDefinition<?> flow) {
flow.handle(new GenericHandler<Order>() {
#Override
public Object handle(Order order, Map<String, Object> headers) {
logger.info("Processing order with id: {}", order.getId());
return null;
}
});
}
}
When I put a message into the "order_input" channel, it's executing the first OrderFlow handler in the main thread and OrderSubFlow handler in TaskExecutor thread, which is expected. But the OrderFlow second handler is also getting executed in TaskExecutor thread. Is this an expected behaviour? Shouldn't OrderFlow second handler be executed in the main thread itself?
Please see the logs below.
INFO 9648 --- [ main] com.example.flows.OrderFlow : Pre-Processing order with id: 10
INFO 9648 --- [lTaskExecutor-1] com.example.flows.OrderSubFlow : Processing order with id: 10
INFO 9648 --- [lTaskExecutor-2] com.example.flows.OrderFlow : Post-Processing order with id: 10
Here is the gateway I'm using
#MessagingGateway
public interface OrderService {
#Gateway(requestChannel="order_input")
Order processOrder(Order order);
}
Please, read a discussion in the https://jira.spring.io/browse/INT-4264. That is really expected behavior. Just because that handler is one more subscriber to that publishSubscribeChannel.
To make what you want is possible with the .routeToRecipients() when one of the recipients is pub-sub with Executor, and another is DirectChannel to continue in the main thread.

Access Spring profiles in spring-junit test classes

I am using Spring 4.3.0.I am writing a SDK in that i am having the following classes,
Providers.java
#ComponentScan
#Service
//#PropertySource("classpath:application.properties")
public class Providers {
#Autowired
ApplicationContext context;
public Providers(){
}
public Providers(ApplicationContext applicationContext){
this.context = applicationContext;
}
//...Other SDK component code
}
ProvidersBuilder.java
public class ProvidersBuilder {
//Set providers property
public Providers build() throws LifecycleException, InsufficientPropertiesException {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext();
StandardEnvironment env = new StandardEnvironment();
context.setEnvironment(env);
if(cond1){
context.getEnvironment().addActiveProfile("profile1");
}
if(cond2){
context.getEnvironment().addActiveProfile("profile2");
}
...etc
context.setParent(null);
context.register(Providers.class);
context.refresh();
Providers Providers = new Providers(context);
return Providers;
}
}
I have following configuration for Spring-Junit test classes,
SpringContextLoader.java
#ComponentScan(basePackages = "com.providers.global")
#PropertySource("classpath:application.properties")
public class SpringContextLoader {
public static void main(String[] args) throws Exception {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(SpringContextLoader.class);
}
}
In one of my test class, I am trying to print all the profiles,
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = SpringContextLoader.class)
public class ProvidersTest{
#Autowired
ApplicationContext context;
#Before
public void beforeMethod() throws Exception {
String[] profiles = context.getEnvironment().getActiveProfiles();
if(profiles != null && profiles.length > 0){
for (String string : profiles) {
logger.info(String.format("Active Profiles in test::%s",string));
}
}
}
#Test
public void activateProviders() throws Exception{
...invoking test call
}
}
In the logs i am able to see only the profiles configured in application.properties, but i would like to get the profiles which are dynamically added in ProvidersBuilder.java.
Basically i would run ProvidersTest only for particular profiles for that i am using the following annotation,
#IfProfileValue(name = "spring.profiles.active", values = { "charging" })
Since application context always returns default profile configured in application.properties this class never get a chance to run.
Could anyone please help me to resolve this issue.Why the profiles added in ProvidersBuilder.java is not available in ProvidersTest.java?
**Edit 1 **
SpringContextLoader.java
#ComponentScan(basePackages = "com.providers.global")
#PropertySource("classpath:application.properties")
public class SpringContextLoader {
#Bean(name = "ConfigApplicationContext")
public AnnotationConfigApplicationContext applicationContext() {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext();
return context;
}
}
Now we are not creating new AnnotationConfigApplicationContext anywhere in application.
ProvidersBuilder.java
public class ProvidersBuilder {
#Autowired
#Qualifier("ConfigApplicationContext")
public AnnotationConfigApplicationContext context;
//Set providers property
public Providers build() throws LifecycleException, InsufficientPropertiesException {
context.setEnvironment(env); **Here i am getting getting NullPointerException**
if(cond1){
context.getEnvironment().addActiveProfile("profile1");
}
if(cond2){
context.getEnvironment().addActiveProfile("profile2");
}
...etc
context.setParent(null);
context.register(Providers.class);
context.refresh();
Providers Providers = new Providers(context);
return Providers;
}
}
In the ProvidersBuilder.java while getting "AnnotationConfigApplicationContext context" using #Autowired it returns null.

Strange situation with #Autowired

I have strange situation with #Autowired
App's main class:
#Configuration
#EnableAutoConfiguration
#SpringBootApplication
#ComponentScan({"khartn", "khartn.torrentsuploader.processor"})
public class NewMain implements CommandLineRunner {
public static void main(String[] args) {
SpringApplicationBuilder builder = new SpringApplicationBuilder(NewMain.class);
builder.headless(false);
ConfigurableApplicationContext context = builder.run(args);
}
#Override
public void run(String... args) throws Exception {
}
}
Component class:
#Component("MyDirectoryReader")
public class MyDirectoryReader {
public MyDirectoryReader ( ) {
System.out.println("qqqqqqqqqqqqqqq");
}
public void readDir() {
try {
String initialPathStr = NewJFrame.jTextField1.getText();
System.out.println("initialPathStr " + initialPathStr);
Path dir = FileSystems.getDefault().getPath(initialPathStr);
DirectoryStream<Path> stream = Files.newDirectoryStream(dir, "*.{torrent}");
for (Path path : stream) {
System.out.println(path.getFileName());
}
stream.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
When application starting, I see, what MyDirectoryReader class is initialized:
2015-04-11 21:42:29.405 INFO 9375 --- [.NewMain.main()] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext#13d6044f: startup date [Sat Apr 11 21:42:29 SAMT 2015]; root of context hierarchy
qqqqqqqqqqqqqqq
Config class:
#Configuration
#ComponentScan({"khartn", "khartn.torrentsuploader.processor"})
public class AppConfig {
#Bean(initMethod = "init")
public NewJFrame mainForm() {
System.out.println("init mainForm");
return new NewJFrame();
}
}
And in NewJFrame class have autowired field
public class NewJFrame extends javax.swing.JFrame {
#Autowired
#Qualifier(value = "MyDirectoryReader")
MyDirectoryReader myDirectoryReader;
But when NewJFrame showed and button pressed,
private void jButton2ActionPerformed(java.awt.event.ActionEvent evt) {
if (myDirectoryReader == null) {
System.out.println("myDirectoryReader is null");
}
// myDirectoryReader.readDir();
}
then myDirectoryReader is null.
Why MyDirectoryReader is initialized as Component, but not autowired to the field?
According to the code from the init method, you make the call new NewJFrame().setVisible(true);, however, when you create an object yourself, Spring doesn't know that and autowiring doesn't work. You have to use the Spring bean object itself. I think, if you change that line to this.setVisible(true), it should work correctly (since the init method is executed when the bean is instantiated).

Resources