How to force persisting data during execution of camel processors rather than the end of the route? - spring-boot

When i toogle a breakpoint beside the repository.saveAndFlush and during de debug mode i see that it returne a new client objet with new Id but when i check in the data base i do not find that client. However, if i do a resume (F8 with eclipse) then i re-check the DB i find the client.
So How to force persisting data during execution of camel processors rather than the end of the route?
#Component
public class myRoute extends RouteBuilder {
#Autowired Processor validationDatasProcessor;
#Autowired Processor clientProcessor;
#Autowired Processor endCientProcessor;
#Override
public void configure() throws Exception {
from("queueIn")
.id("route_processing").messageHistory().transacted()
.log(LoggingLevel.DEBUG, log, "reception").pipeline()
.process(validationDatasProcessor)
.id(validationDatasProcessor.getClass().getSimpleName().toLowerCase())
.process(clientProcessor)
.id(clientProcessor.getClass().getSimpleName().toLowerCase())
.process(endCientProcessor).id(endCientProcessor.getClass().getSimpleName().toLowerCase())
.to("outputQueue")
.end();
}
}
Processors:
#Component
public class ValidationDatasProcessor implements Processor {
#Autowired ObjectMapper objectMapper;
#Autowired ClientRepository clientRepository;
#Override
public void process(Exchange exchange) throws Exception {
String clientString = exchange.getIn().getBody(String.class);
Client client = objectMapper.readValue(clientString, Client.class);
clientRepository.saveAndFlush(client)
exchange.setOut(generateOutMessage(client, exchange.getContext()));
}
Message generateOutMessage(Client client, CamelContext camelContext) throws JsonProcessingException {
DefaultMessage outMessage = new DefaultMessage(camelContext);
outMessage.setBody(objectMapper.writeValueAsString(client), String.class);
return outMessage;
}
}
#Component
public class ClientProcessor implements Processor {
#Autowired ObjectMapper objectMapper;
#Autowired ClientRepository clientRepository;
....
#Override
public void process(Exchange exchange) throws Exception {
String clientString = exchange.getIn().getBody(String.class);
Client client = objectMapper.readValue(clientString, Client.class);
client.setAccessDate(LocalDateTime.now);
clientRepository.saveAndFlush(client)
exchange.setOut(generateOutMessage(client, exchange.getContext()));
}
Message generateOutMessage(Client client, CamelContext camelContext) throws JsonProcessingException {
DefaultMessage outMessage = new DefaultMessage(camelContext);
outMessage.setBody(objectMapper.writeValueAsString(client), String.class);
return outMessage;
}
}

Your entire route is a transacted; which means the whole route is under a transaction scope. A commit will be done only after the whole route is executed.
if you want to execute a processor outside the transaction boundary, split the route and use a seda endpoint. seda are asynchronous and start new threads. They won't participate in the active transaction boundary.
Commiting parts in the middle of a transaction scope doesn't sound like a great idea. Perhaps your rounte needs to be split into multiple fragments.
This documentation might help you understand them better.

Related

Spring cloud stream : how to use #Transactional with new Consumer<> functional programming model

I have StreamListener which I would like to replace using the new functional model and Consumer <>. Unfortunately, I don't know how to transfer #Transactional to new model:
#Transactional
#StreamListener(PaymentChannels.PENDING_PAYMENTS_INPUT)
public void executePayments(PendingPaymentEvent event) throws Exception {
paymentsService.triggerInvoicePayment(event.getInvoiceId());
}
I have tired certain things. Sample code below. I added logging messages to a different queue for tests. Then I throw an exception to trigger a rollback. Unfortunately, messages are queued even though they are not there until the method is completed (I tested this using brakepoints). It seems that the transaction was automatically committed despite the error.
#Transactional
#RequiredArgsConstructor
#Component
public class functionalPayment implements Consumer<PendingPaymentEvent> {
private final PaymentsService paymentsService;
private final StreamBridge streamBridge;
public void accept(PendingPaymentEvent event) {
paymentsService.triggerInvoicePayment(event.getInvoiceId());
streamBridge.send("log-out-0",event);
throw new RuntimeException("Test exception to rollback message from log-out-0");
}
}
Configuration:
spring.cloud.stream.rabbit.bindings.functionalPayment-in-0.consumer.queue-name-group-only=true
spring.cloud.stream.rabbit.bindings.functionalPayment-in-0.consumer.declare-exchange=true
spring.cloud.stream.rabbit.bindings.functionalPayment-in-0.consumer.bind-queue=true
spring.cloud.stream.rabbit.bindings.functionalPayment-in-0.consumer.transacted=true
spring.cloud.stream.source=log
spring.cloud.stream.bindings.log-out-0.content-type=application/json
spring.cloud.stream.bindings.log-out-0.destination=log_a
spring.cloud.stream.bindings.log-out-0.group=log_a
spring.cloud.stream.rabbit.bindings.log-out-0.producer.declare-exchange=true
spring.cloud.stream.rabbit.bindings.log-out-0.producer.bind-queue=true
spring.cloud.stream.rabbit.bindings.log-out-0.producer.queue-name-group-only=true
spring.cloud.stream.rabbit.bindings.log-out-0.producer.binding-routing-key=log
spring.cloud.stream.rabbit.bindings.log-out-0.producer.transacted=true
spring.cloud.stream.rabbit.bindings.log-out-0.producer.exchange-type=direct
spring.cloud.stream.rabbit.bindings.log-out-0.producer.routing-key-expression='log'
Have you tried something along the lines of
#Transactional
public class ExecutePaymentConsumer implements Consumer<PendingPaymentEvent> {
public void accept(PendingPaymentEvent event) {
paymentsService.triggerInvoicePayment(event.getInvoiceId());
}
}
. . .
#Bean
public ExecutePaymentConsumer executePayments() {
return new ExecutePaymentConsumer();
}

Apache camel dynamic routing

I have following Apache camel rest service(/sales) that internally calls another rest service(/getOrders) and get list of objects. Am able to print JSON response in the processor but getting java objects in response while trying from postman. Could anyone pls help me to resolve the issue. Attaching the response log for ref..
#Component
public class ApplicationResource extends RouteBuilder {
#Autowired
private OrderService service;
#BeanInject
private OrderProcessor processor;
#Override
public void configure() throws Exception {
restConfiguration().component("servlet").port(9090).host("localhost");
rest().get("/getOrders").produces(MediaType.APPLICATION_JSON_VALUE).route().setBody(() -> service.getOrders());
rest().get("/sales").produces(MediaType.APPLICATION_JSON_VALUE).route()
.setHeader(Exchange.CONTENT_TYPE, constant("application/json"))
.toD("http://localhost:9090/getOrders?bridgeEndpoint=true").convertBodyTo(String.class).marshal()
.json(JsonLibrary.Jackson, Order.class).to("log:foo?showHeaders=true");;
;
}
}
You should remove the last .endRest() on "direct:bye" route.
I think you get the rest response before calling your Processor.
This works for me.
First, I needed to set the bindingMode as RestBindingMode.json in the restConfiguration.
Secondly, instead of marshal(), you need to use unmarshal().
Third, since you are returning a list of orders, .json(JsonLibrary.Jackson, Order.class) will not be sufficient to unmarshal the list of orders. You need to use a custom format which will be able to unmarshal the list of orders into a json array. This you need to do using JacksonDataFormat format = new ListJacksonDataFormat(Order.class);
#Override
public void configure() {
JacksonDataFormat format = new ListJacksonDataFormat(Order.class);
restConfiguration().component("servlet").port(9090).host(localhost).bindingMode(RestBindingMode.json);
rest()
.get("/getOrders")
.produces(MediaType.APPLICATION_JSON_VALUE)
.route()
.process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
exchange.getMessage().setBody(service.getOrders());
}})
.to("log:getOrders?showHeaders=true&showBody=true");
rest()
.get("/sales")
.produces(MediaType.APPLICATION_JSON_VALUE)
.route()
.setHeader(Exchange.CONTENT_TYPE, constant("application/json"))
.toD("http://localhost:9090/getOrders?bridgeEndpoint=true")
.unmarshal(format)
.to("log:sales?showHeaders=true&showBody=true");
}
Solvedddd !!! i did two things as follows,May be use full for some one
1,bindingMode(RestBindingMode.auto) - RestBindingMode changes to auto
from json
2, Added this in the main
service(/getOrders).marshal().json(JsonLibrary.Jackson);
#Component
public class ApplicationResource extends RouteBuilder {
#Autowired
private OrderService service;
#BeanInject
private OrderProcessor processor;
#Override
public void configure() throws Exception {
restConfiguration().component("servlet").port(9090).host("localhost").bindingMode(RestBindingMode.auto);
rest().get("/getOrders").produces(MediaType.APPLICATION_JSON_VALUE).route().setBody(() -> service.getOrders())
.marshal().json(JsonLibrary.Jackson);
rest().get("/sales").produces(MediaType.APPLICATION_JSON_VALUE).route()
.setHeader(Exchange.CONTENT_TYPE, constant("application/json"))
.toD("http://localhost:9090/getOrders?bridgeEndpoint=true").convertBodyTo(String.class)
.log("body = ${body}");
;
;
}
}

WebSocket messages are not delivered all the times

I have an application with WebSockets using spring-boot application as backend and Stomp/SockJS in the client side, the spring-boot application consume JMS queue messages and notify the changes to the right user. What is the problem? Sometimes works and sometimes doesn't work, same code and users could work or not.
The client side code is a bit more difficult to copy here because it's integrate over react/redux application but basically is a subscription to two different channels, both defined in the configuration of Spring. The sessions are created correctly according to debug information but just sometimes the message is processed to send it to connected sessions.
This is the configuration class for Spring.
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfiguration implements WebSocketMessageBrokerConfigurer {
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry
.addEndpoint("/stomp")
.setAllowedOrigins("*")
.withSockJS();
}
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry
.setApplicationDestinationPrefixes("/app")
.enableSimpleBroker("/xxxx/yyyy", "/ccccc");
}
#Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.interceptors(new ChannelInterceptor() {
#Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor accessor =
MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
if (StompCommand.CONNECT.equals(accessor.getCommand())) {
Object raw = message
.getHeaders()
.get(SimpMessageHeaderAccessor.NATIVE_HEADERS);
if (raw instanceof Map) {
Object name = ((Map<?,?>) raw).get("email");
if (name instanceof LinkedList) {
String user = ((LinkedList<?>) name).get(0).toString();
accessor.setUser(new User(user));
}
}
}
return message;
}
});
}
}
This is the JMS listener to process queue message and send it to specific user.
#Component
public class UserEventListener {
private final Logger logger = LoggerFactory.getLogger(getClass());
private final SimpMessagingTemplate template;
#Autowired
public UserEventListener(SimpMessagingTemplate pTemplate) {
this.template = pTemplate;
}
#JmsListener(destination="user/events")
public void onStatusChange(Map<String, Object> props) {
if (props.containsKey("userEmail")) {
logger.debug("Event for user received: {}", props.get("userEmail"));
template.convertAndSendToUser((String)props.get("userEmail"), "/ccccc", props);
}
}
}
Edit 1:
After more debugging the times when doesn't work the "session" for WebSocket seems to be lost by Spring configuration. I don't see any log information about "Disconnected" messages or something similar, besides if I debug remotely the server when this happens the problem doesn't appears during debugging session. Some idea? The class from Spring where session disappear is DefaultSimpUserRegistry.
After more research I found a question with the same problem and the solution here. Basically the conclusion is this:
Channel interceptor is not the right place to authenticate user, we need to change it with a custom handshake handler.

How to involve a Collection on a spring transaction?

I currently have a spring application with hibernate and a PlataformTransactionManager running on Jboss/wildfly.
Some of the methods that manipulate the database also call a bean which contains a LinkedBlockingQueue. This queue stores logging messages that are periodically dispatched to someplace else on another thread (using simple spring #Scheduler).
Would it be possible to make my queue (inside a bean) transactional? ie. if the transaction rollback would I be able to "undo" any operations made on my Collection? What's the best strategy to implement this ?
So, in short something like:
#Service
#Transactional
public PersonService {
#Autowired
EntityManager EM;
#Autowired
LoggingBuffer logger;
public void addPerson(String name) {
EM.persist(new Person(.....));
logger.add("New person!");
// A rollback here via some thrown exception would not affect the queue
}
}
#Component
public class LoggingBuffer {
private Queue<String> q= new LinkedBlockingQueue<String>();
public add(String msg){
q.add(msg);
}
}
Try something like this
#Transactional
public void addPerson(String name) {
EM.persist(new Person(.....));
//logger.add("New person!");
// A rollback here via some thrown exception would not affect the queue
}
public void wrapAddPerson(String name){
List<String> localBuffer = new ArrayList<>();
try{
addPerson(name);
localBuffer.add(".....");
}catch(Exception e)
{
localBuffer.clear();
}
finally{
localBuffer.forEach(logger::add);
}
}

Spring SimpMessagingTemplate

I have an application which receive some data from RabbitMQ. Everything works fine, I mean in class where I have annotation #EnableScheduling.
#Scheduled(fixedDelay = 5000)
public void volumeGraphData() {
Random r = new Random();
Graph graph = new Graph();
graph.setVolume(r.nextInt(500));
String json = gson.toJson(graph);
MessageBuilder<byte[]> messageBuilder = MessageBuilder.withPayload(json.getBytes());
simpMessagingTemplate.send("/" + volumeGraph, messageBuilder.build());
}
But when I would like to process messages received by Queue Listener from RabbitMQ (this works too) and pass them through to specific context for Stomp WebSocket using SimpMessagingTemplate I cannot do that. SimpMessagingTemplate is defined in dispatcher-servlet.xml, but configuration related with RabbitMQ is in root context. I tried to move everything to one context, but it does not work. Anyone has similar case that one I have ?
I finally managed to fix this. So, basically you need move your beans related with Spring Messaging/WebSocket to one common bean.
That's why in my root context I have such lines :
<!-- Fix for IntelliJ 13.1 https://youtrack.jetbrains.com/issue/IDEA-123964 -->
<context:component-scan base-package="org.springframework.web.socket.config"/>
where in package pl.garciapl.program.service.config is located class responsible for configuration of WebSockets :
#Configuration
#EnableWebSocketMessageBroker
#Component("messageBroker")
public class MessageBrokerConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void registerStompEndpoints(StompEndpointRegistry stompEndpointRegistry) {
stompEndpointRegistry.addEndpoint("/test").withSockJS();
}
#Override
public void configureMessageBroker(MessageBrokerRegistry messageBrokerRegistry) {
}
#Override
public void configureClientInboundChannel(ChannelRegistration channelRegistration) {
}
#Override
public void configureClientOutboundChannel(ChannelRegistration channelRegistration) {
}
#Override
public boolean configureMessageConverters(List<MessageConverter> messageConverters) {
messageConverters.add(new MappingJackson2MessageConverter());
return false;
}
}
Remember to store your beans which use SimpMessagingTemplate in the same context where you defined this above class.

Resources