Spring integration trigger timer events [closed] - spring

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 3 years ago.
Improve this question
I am still new to Spring Integration so please bear with me.
I have a use case where for each event received we have to create a set of timers that will be executed when it reaches that time frame. I am looking into Delayer but not sure if it will satisfy the above condition
Say for example when we receive a schedule event for a flight we will process, persist as per the business logic and will create 2 timers for the flight that will be executed after an hour. So when the clock reaches that hour mark it will perform some defined action.
I am thinking of "delayer" with a persistent message store but not sure if it is scalable for a huge load of 20k timers at a given time.
For debugging purposes I would also like to see the history of timer that are successfully executed and the details of the timer.
Please recommend a good approach.

If I understood correctly : You are looking for Event based Dynamic Schedulers.
You can use REST (Event) & Quartz schedulers (Scheduling). Upon hitting URL localhost/schedule it will schedule the event as per the time give in your REST request.
Refer to below code sample
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.restro.jobs.request.ScheduleJobsRequest;
import com.restro.jobs.service.SchedulerService;
#Controller
public class JobsScheduleController implements ApplicationContextAware{
#Autowired
SchedulerService service;
#Autowired
SchedulerFactoryBean schedulerFactoryBean;
ApplicationContext applicationContext;
#RequestMapping(value = "/schedule", method = RequestMethod.POST, produces = { "application/json" })
public #ResponseBody void schedule(#RequestBody ScheduleJobsRequest scheduleJobsRequest)
throws JsonProcessingException, ClassNotFoundException, SchedulerException {
Scheduler scheduler = schedulerFactoryBean.getScheduler();
scheduler.getContext().put("applicationContext", applicationContext);
service.scheduleJobs(scheduleJobsRequest.getJobName(), scheduleJobsRequest.getGroup(),
scheduleJobsRequest.getCronExpression(), scheduler);
System.out.println("scheduled");
}
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
Sample REST Scheduling request
{
"jobName":"OrderOneSettlementJob",
"group":"order",
"cronExpression":"0/30 * * * * ?"
}

Related

Spring - micrometer + opentelemetry-exporter-otlp

Is it possible to configure micrometer to send traces to otel container in spring?
I easily configured sending spans to Zipkin and Wavefront:
https://docs.spring.io/spring-boot/docs/3.1.0-SNAPSHOT/reference/htmlsingle/#actuator.micrometer-tracing.tracers but there is nothing about exporting to Otel.
Micrometer documentation also does not mention about exporting spans to otel container https://micrometer.io/docs/tracing#_using_micrometer_tracing_directly
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter;
import io.opentelemetry.sdk.trace.export.SpanExporter;
/**
* As of SpringBoot 3.0.2 the inclusion of io.micrometer:micrometer-tracing-bridge-otel and
* io.opentelemetry:opentelemetry-exporter-otlp is not sufficient to bootstrap the SpanExporter. Adding
* io.opentelemetry:opentelemetry-sdk-extension-autoconfigure also does not help. Hence this solution which will
* probably be redundant one day.
*/
#Configuration
public class OpenTelemetryConfig {
#Value("${otel.exporter.otlp.traces.endpoint:http://localhost:4317}")
private String tracesEndpoint;
#Bean
public SpanExporter spanExporter() {
return OtlpGrpcSpanExporter.builder().setEndpoint(tracesEndpoint).build();
}
}
I also found the following necessary if you want to use io.opentelemetry.instrumentation:opentelemetry-jdbc (and probably others) as it relies on GlobalOpenTelemetry.get(). This is forcing it to be the instance produced by the micrometer-tracing-bridge-otel.
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.annotation.Configuration;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.OpenTelemetry;
#Configuration
public class GlobalOpenTelemetrySetter implements BeanPostProcessor {
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
if (bean instanceof OpenTelemetry openTelemetry) {
GlobalOpenTelemetry.set(openTelemetry);
}
return bean;
}
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
I worry that this could have startup race conditions but is working for me at the moment. I hope the Spring team can provide proper clarification at some point.

How to access matching rule details in Camunda DMN in a Spring Boot project?

My Spring Boot app connects to the Camunda BPMN & DMN. When a user clicks on Approve/Reject button in UI screen, a REST call triggers one BPMN workflow (in Camunda Spring Boot app) which internally triggers a DMN to find the output based on some business rules mapped in DMN input columns. We need to find the exact matching rules details like list of matching rules - input values, output values, matching rule ID, etc.
There are no inbuilt methods present in process engine and dmn engine and hence need to know if there is a way to manually configure such capabilities to the default process engine that comes as part of Spring Boot auto-configuration.
Camunda version:
7.13.0
In a Spring Boot app, if we have these dependencies in pom.xml - camunda-bpm-spring-boot-starter, camunda-bpm-spring-boot-starter-webapp, camunda-bpm-spring-boot-starter-rest, a default Process Engine will be auto-configured during the startup of the Spring Boot app. But this Process Engine does not by default enable the Rule details. So we configure a custom Process Engine plugin which will be applied on top of the default Process Engine in order to get the matching rule details every time DMN is hit.
import org.camunda.bpm.spring.boot.starter.annotation.EnableProcessApplication;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
#EnableProcessApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
}
Customize Process Engine:
import org.camunda.bpm.engine.impl.cfg.ProcessEnginePlugin;
import org.camunda.bpm.spring.boot.starter.configuration.Ordering;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
#Configuration
public class CamundaConfig {
#Bean
#Order(Ordering.DEFAULT_ORDER + 1)
public static ProcessEnginePlugin customProcessEnginePluginConfig() {
return new CustomProcessEnginePlugin();
}
}
Our CustomProcessEnginePlugin will extend AbstractCamundaConfiguration and override postInit() method. This postInit() method adds a DmnDecisionTableEvaluationListener. This listner triggers an event everytime DMN is hit with matching rule and has notify() method with event details which contains all the DMN details like dmnID and matching rules detail.
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.camunda.bpm.dmn.engine.delegate.DmnDecisionTableEvaluationEvent;
import org.camunda.bpm.dmn.engine.delegate.DmnDecisionTableEvaluationListener;
import org.camunda.bpm.dmn.engine.delegate.DmnEvaluatedDecisionRule;
import org.camunda.bpm.dmn.engine.delegate.DmnEvaluatedInput;
import org.camunda.bpm.dmn.engine.impl.DefaultDmnEngineConfiguration;
import org.camunda.bpm.engine.impl.cfg.ProcessEngineConfigurationImpl;
import org.camunda.bpm.engine.variable.value.TypedValue;
import org.camunda.bpm.spring.boot.starter.configuration.Ordering;
import org.camunda.bpm.spring.boot.starter.configuration.impl.AbstractCamundaConfiguration;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
#Slf4j
#Component
#Order(Ordering.DEFAULT_ORDER + 1)
public class CustomProcessEnginePlugin extends AbstractCamundaConfiguration {
#Override
public void postInit(ProcessEngineConfigurationImpl processEngineConfig) {
DefaultDmnEngineConfiguration dmnEngineConfig = processEngineConfig.getDmnEngineConfiguration();
dmnEngineConfig.customPostDecisionTableEvaluationListeners(Collections.singletonList(new DmnDecisionTableEvaluationListener(){
#Override
public void notify(DmnDecisionTableEvaluationEvent event) {
String dmnID = event.getDecisionTable().getKey();
Map<String, TypedValue> dmnInput = event.getInputs().stream().collect(Collectors.toMap(DmnEvaluatedInput::getName, DmnEvaluatedInput::getValue));
List<DmnEvaluatedDecisionRule> matchingRuleList = new ArrayList<>();
matchingRuleList = event.getMatchingRules();
log.info("DMN ID = {}", dmnID);
log.info("DMN Input = {}", dmnInput);
if(null != matchingRuleList) {
log.info("DMN Matched Rules = {}", matchingRuleList.size());
if(!matchingRuleList.isEmpty()) {
for(DmnEvaluatedDecisionRule rule : matchingRuleList) {
log.info("DMN Matching Rule ID = {}", rule.getId());
log.info("DMN Output = {}", rule.getOutputEntries());
}
}
else {
log.info("DMN Output = No matching rule found");
}
}
log.info("************************************************************************************************");
}
}));
dmnEngineConfig.buildEngine();
processEngineConfig.setDmnEngineConfiguration(dmnEngineConfig);
}
}
There is another way to get the matching rule details - by implementing ProcessEnginePlugin and overriding postProcessEngineBuild() and preInit() methods instead of extending AbstractCamundaConfiguration and overriding postInit() method as above.

How to close spring boot with routing properly

I'm trying to use a routing to stop my spring boot application with the code below
with
#GetMapping("/close/")
fun terminate() {
exitProcess(0)
}
but the test server has a different API, so I can't use this code (It's shutdown a whole system)
My question is: how to stop only my spring boot application (replace exitProcess(0))
You can do it using Actuator's shutdown for example.
Find an example here.
Or you can just close your Application Context and that will work.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
#RestController
public class SomeRestController {
#Autowired
ApplicationContext applicationContext;
#GetMapping("/close")
public void terminate() {
((AbstractApplicationContext)applicationContext).close();
}
}

Spring webflux, testing `ServerResponse`

How can I test a handler method that receive ServerRequest and return Mono<ServerResponse> ?
I can create a ServerRequest via org.springframework.mock.web.reactive.function.server.MockServerRequest.builder(). And assert on the ServerResponse.statusCode(). However I would like to test the body of this ServerResponse but there is no way to extract it.
ServerResponse response = target.search(MockServerRequest.builder()
.method(HttpMethod.GET)
.build()
).block();
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
//assertThat(response.body()).isNotNull();
I don't want to do a broader test with the WebTestClient, I would like to test all possible cases of response with unit tests.
Thanks
So, it seems that the best solution is to use the WebTestClient. However this one can be used without a running server;
The spring-test module includes a WebTestClient that can be used to test WebFlux server endpoints with or without a running server.
-- https://docs.spring.io/spring/docs/5.0.0.BUILD-SNAPSHOT/spring-framework-reference/html/web-reactive.html#web-reactive-tests
The trick is to use the bindTo..() builder method. In my case bindToRouterFunction(new MyRouter(new MyHandler())) works well
I had exactly the same question, because I don't want to start the entire Spring context for every test. Thanks for the info in the own-answer, this helped me. Here is a complete example including Mockito and JUnit5:
import java.util.Arrays;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.http.MediaType;
import org.springframework.test.web.reactive.server.WebTestClient;
#ExtendWith(MockitoExtension.class)
class UsersHandlerTest {
#Mock
UserRepository userRepository;
WebTestClient client;
#BeforeEach
public void setUp() {
UsersHandler usersHandler = new UsersHandler(userRepository);
Router router = new Router(usersHandler);
Mockito.when(userRepository.findAll()).thenReturn(
Arrays.asList("", ""));
client = WebTestClient.bindToRouterFunction(router.getUsers()).build();
}
#Test
public void getCommands() {
client.get().uri("/users")
.accept(MediaType.APPLICATION_JSON_UTF8)
.exchange()
.expectStatus().isOk()
.expectBody().json("{}");
}
}
I have asked a similar question insisting not to use WebTestClient.
I have ended up answering my own question after discovering that it is possible to cast the response to a type that allows inspecting body (entity).
The magic line (Kotlin) is:
(serverResponse as EntityResponse<Fleet>).entity()
Edit:
So for the case of expecting an empty body, you could do:
assert(serverResponse !is EntityResponse<*>)
Thanks for #andred for pointing this out

Spring 5 reactive websocket and multiple endpoints?

We are doing a little hackathon at work and I wanted to try some new technology to get away from the usual controller.
I started using Spring Webflux with reactive WebSockets and everything is working fine so far. I configured my WebSocket handler as follows:
import my.handler.DomWebSocketHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.HandlerMapping;
import org.springframework.web.reactive.config.WebFluxConfigurer;
import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping;
import org.springframework.web.reactive.socket.WebSocketHandler;
import org.springframework.web.reactive.socket.server.support.WebSocketHandlerAdapter;
import java.util.HashMap;
import java.util.Map;
#Configuration
public class AppConfig implements WebFluxConfigurer {
#Autowired
private WebSocketHandler domWebSocketHandler;
#Bean
public HandlerMapping webSocketMapping() {
Map<String, WebSocketHandler> map = new HashMap<>();
map.put("/event-emitter", domWebSocketHandler);
SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
mapping.setOrder(1);
mapping.setUrlMap(map);
return mapping;
}
#Bean
public WebSocketHandlerAdapter handlerAdapter() {
return new WebSocketHandlerAdapter();
}
}
After some more research, I learned that it is best practice to work with one connection per client.
Furthermore using more than a web-socket per browsing session for the same application seems overkill since you can use pub/sub channels. See answer here
Is there a way to restrict the connections per client and use only one endpoint for all required client "requests" or would it be better to create additional endpoints (like you would with a normal controller)?
Thank you in advance for the help.

Resources