How to intercept Spring Cloud Stream messages? - java

Spring allows interception of messages for many of their products, like RestTemplate and SpringMVC. Is it possible to intercept Spring Cloud Stream messages? For both incoming and outgoing messages.

Was able to intercept inbound and outbound Spring Cloud Stream messages using the GlobalChannelInterceptor annotation and ChannelInterceptor interface. See sample below.
import org.springframework.integration.config.GlobalChannelInterceptor;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.support.ChannelInterceptor;
import org.springframework.stereotype.Component;
#Component
#GlobalChannelInterceptor
public class Interceptor implements ChannelInterceptor {
private final Logger log = LoggerFactory.getLogger(Interceptor.class);
#Override
public Message<?> preSend(Message<?> msg, MessageChannel mc) {
log.info("In preSend");
return msg;
}
#Override
public void postSend(Message<?> msg, MessageChannel mc, boolean bln) {
log.info("In postSend");
}
#Override
public void afterSendCompletion(Message<?> msg, MessageChannel mc, boolean bln, Exception excptn) {
log.info("In afterSendCompletion");
}
#Override
public boolean preReceive(MessageChannel mc) {
log.info("In preReceive");
return true;
}
#Override
public Message<?> postReceive(Message<?> msg, MessageChannel mc) {
log.info("In postReceive");
return msg;
}
#Override
public void afterReceiveCompletion(Message<?> msg, MessageChannel mc, Exception excptn) {
log.info("In afterReceiveCompletion");
}
}

Not sure what you mean by interception here - both examples you give are not message-based :).
But you want to get access to the full message, you can use that as argument to a #StreamListener or #ServiceActivator-annotated method. Also, Spring Cloud Stream allows you to set up a full Spring Integration pipeline, so you can add advices and everything you need - see here: https://github.com/spring-projects/spring-integration-java-dsl/wiki/Spring-Integration-Java-DSL-Reference.
I would encourage you to take a look at the Spring Integration reference as well http://docs.spring.io/autorepo/docs/spring-integration/4.2.6.RELEASE/reference/html/. Spring Cloud Stream injects the channels automatically, and from there you have full freedom on how you construct your pipeline.
Hope this helps,
Marius

Related

Spring AOP does not intercept JMS MessageListener onMesage() call

I am trying to intercept JMS MessageListener onMessage() method using Spring AOP, but my advice is not getting called. I have created an annotation #LoggingInfo:
#Target (ElementType.METHOD)
#Retention (RetentionPolicy.RUNTIME)
public #interface LoggingInfo {
}
Aspect class : I have also tried with the commented ponitcut definition but still advice is not getting called.
#Aspect
#Component
public class LoggingAspect {
#Pointcut("#annotation(LoggingInfo)")
private void annotationPointCut() {
}
//#Pointcut ("execution(* *(..))”)
//#Pointcut ("execution( void *.onMessage (javax.jms.Message))")
#Pointcut("execution( void javax.jms.MessageListener.onMessage(javax.jms.Message))")
private void onMessagePointCut() {
}
#AfterReturning("onMessagePointCut() && annotationPointCut()")
public void loggingAdvice(JoinPoint jp ) {
System.out.println("logging advice method called successfully");
}
}
MessageReceiver.java
#Component
public class MessageReceiver implements MessageListener {
#Override
#LoggingInfo
public void onMessage(Message message) {
System.out.println("Jms message processed successfully");
}
}
All other configuration to setup the application are done properly and onMessage() is processing the incoming message successfully. Also #ComponentScan annotations is mention in application class to scan all the beans.
Is there anything can be done to achieve this using Spring AOP?

How to create an interceptor for Apache Camel Route in Spring boot application

I have a simple route defined and for this route I want to define an outgoing interceptor so that on any request done, I would invoke an enricher that sets specific header.
from("direct:remoteService")
.routeId("direct-route")
.tracing()
.log(">>> ${body.id}")
.log(">>> ${body.name}")
.process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
MyBean bodyIn = (MyBean) exchange.getIn().getBody();
ExampleServices.example(bodyIn);
exchange.getIn().setBody(bodyIn);
}
})
.setHeader(Exchange.HTTP_RESPONSE_CODE, constant(200));
I've tried to define an implementation of AbstractPhaseInterceptor but it is not invoked. What am I doing wrong?
#OutInterceptors
public class HeaderEnricherInterceptor extends AbstractPhaseInterceptor<Message> {
public HeaderEnricherInterceptor() {
super(Phase.POST_PROTOCOL);
}
#Override
public void handleMessage(Message message) {
log.debug("Message interceptor start enrichment...");
}
}
You are using an interceptor used by apache cxf, I don't think there is such a use in camel. can you check out https://camel.apache.org/components/3.16.x/eips/intercept.html

Catching publish errors in GCP PubSub and Spring Boot

I have a Spring Boot application which needs to occasionally publish messages to GCP PubSub. I implemented it following the instructions on the spring boot page (https://spring.io/guides/gs/messaging-gcp-pubsub/) so I have implemented the following configuration file:
#Configuration
public class PubSubConfiguration {
#Value("${myprog.pubsub.sms-topic}")
private String topic;
#Bean
#ServiceActivator(inputChannel = "pubsubOutputChannel")
public MessageHandler messageSender(PubSubTemplate pubsubTemplate) {
return new PubSubMessageHandler(pubsubTemplate, this.topic);
}
#MessagingGateway(defaultRequestChannel = "pubsubOutputChannel")
public interface PubsubOutboundGateway {
void sendToPubsub(String text);
}
}
From my rest controller, I autowire the message gateway and call sendToPubsub:
#RequestMapping("/api/stuff")
#RestController
public class StuffController {
PubSubConfiguration.PubsubOutboundGateway messagingGateway;
#Autowired
public StuffController(#SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") PubSubConfiguration.PubsubOutboundGateway messagingGateway) {
this.messagingGateway = messagingGateway;
}
#RequestMapping(method = RequestMethod.POST, path = "/go")
public ResponseEntity<String> send() {
messagingGateway.sendToPubsub("TEST");
return new ResponseEntity<>("Ok!", HttpStatus.OK);
}
}
This works, however due to our particular use case I would like to respond with an error if publishing fails. If, for example, I configure a non-existent topic I would like to return a 500 error whereas it currently returns 200 and throws an exception asynchronously later. Is there any way I can access a future at the point of publishing?
Spring Cloud GCP PubSub implementation uses Spring Integration framework and rely on it. For this, your send to PubSub method have to throw an exception as described in the Spring integration documentation
#MessagingGateway(defaultRequestChannel = "pubsubOutputChannel")
public interface PubsubOutboundGateway {
void sendToPubsub(String text) throws MessagingException;
}

How to implement a custom WebSocket subprotocol with Spring

I have to add support for a custom WebSocket subprotocol (so not STOMP) in a Spring Boot application, but I'm having a very hard time understanding what I need to provide and what Spring already has.
This is how far I got:
#Configuration
#EnableWebSocket
public class WebSocketAutoConfiguration implements WebSocketConfigurer {
public void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {
webSocketHandlerRegistry.addHandler(this.webSocketHandler(), new String[]{endpointUrl});
}
#Bean
public WebSocketHandler webSocketHandler() {
ExecutorSubscribableChannel clientInboundChannel = new ExecutorSubscribableChannel();
ExecutorSubscribableChannel clientOutboundChannel = new ExecutorSubscribableChannel();
SubProtocolWebSocketHandler subProtocolWebSocketHandler = new SubProtocolWebSocketHandler(clientInboundChannel, clientOutboundChannel);
subProtocolWebSocketHandler.addProtocolHandler(new SubProtocolHandler() {
public List<String> getSupportedProtocols() {
return Collections.singletonList("custom-protocol");
}
public void handleMessageFromClient(WebSocketSession session, WebSocketMessage<?> message, MessageChannel outputChannel) throws Exception {
session.sendMessage(new TextMessage("some message"));
}
public void handleMessageToClient(WebSocketSession session, Message<?> message) throws Exception {
}
public String resolveSessionId(Message<?> message) {
return UUID.randomUUID().toString();
}
public void afterSessionStarted(WebSocketSession session, MessageChannel outputChannel) throws Exception {
System.out.println("SESSION STARTED");
}
public void afterSessionEnded(WebSocketSession session, CloseStatus closeStatus, MessageChannel outputChannel) throws Exception {
session.close();
System.out.println("SESSION ENDED");
}
});
return subProtocolWebSocketHandler;
}
}
This works, in the sense that handleMessageFromClient does get triggered on a web socket message, but I fail to understand the purpose of MessageChannel outputChannel and handleMessageToClient.
Is it possible to get the PerConnectionWebSocketHandler semantics with SubProtocolWebSocketHandler?
The documentation around this is basically non-existent e.g. the docs for handleMessageToClient say:
Handle the given {#link Message} to the client associated with the given WebSocket session.
Well, fantastic. And the STOMP implementations are mind-boggling, so they're not very usable as a guideline.
Any example, broad steps or anything, really, would be much appreciated.
Turns out it is exceptionally easy. No need to mess with SubProtocolWebSocketHandler at all. The only requirement is that the provided WebSocketHandler implements SubProtocolCapable.
public class CustomHandler implements WebSocketHandler, SubProtocolCapable {
...
}
That's all. To make a PerConnectionWebSocketHandler, it's enough to simply extend it and implement SubProtocolCapable:
public class CustomHandler extends PerConnectionWebSocketHandler implements SubProtocolCapable {
...
}

Spring Integration manually publish message to channel

I'm in the process of learning how to use the Java Spring Framework and started experimenting with Spring Integration. I'm trying to use Spring Integration to connect my application to an MQTT broker both to publish and subscribe to messages but I'm having trouble finding a way to manually publish messages to an outbound channel. If possible I want to build it using notations in the java code exclusively rather than xml files defining beans and other related configuration.
In every example I've seen the solution to manually publishing a message seems to be to use a MessagingGateway Interface and then use the SpringApplicationBuilder to get the ConfigurableApplicationContext to get a reference to the gateway interface in the main method. The reference is then used to publish a message. Would it be possible to use AutoWired for the interface instead? In my attempts I just get a NullPointer.
My aim is to build a game where I subscribe to a topic to get game messages and then whenever the user is ready to make the next move, publish a new message to the topic.
Update:
This is one of the examples I've been looking at of how to setup an outbound channel: https://docs.spring.io/spring-integration/reference/html/mqtt.html
Update 2 after answer from Gary Russel:
This is some example code I wrote after looking at examples which gets me a NullPointer when using #AutoWired for the Gateway when running gateway.sendToMqtt in Controller.java. What I want to achieve here is to send an mqtt message manually when a GET request is handled by the controller.
Application.java
#SpringBootApplication
public class Application {
public static void main(String[] args){
SpringApplication.run(Application.class, args);
}
}
Controller.java
#RestController
#RequestMapping("/publishMessage")
public class Controller {
#Autowired
static Gateway gateway;
#RequestMapping(method = RequestMethod.GET)
public int request(){
gateway.sendToMqtt("Test Message!");
return 0;
}
}
MqttPublisher.java
#EnableIntegration
#Configuration
public class MqttPublisher {
#Bean
public MqttPahoClientFactory mqttClientFactory(){
DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
factory.setServerURIs("tcp://localhost:1883");
return factory;
}
#Bean
#ServiceActivator(inputChannel = "mqttOutboundChannel")
public MessageHandler mqttOutbound(){
MqttPahoMessageHandler messageHandler =
new MqttPahoMessageHandler("clientPublisher", mqttClientFactory());
messageHandler.setAsync(true);
messageHandler.setDefaultTopic("topic");
return messageHandler;
}
#Bean
public MessageChannel mqttOutboundChannel(){
return new DirectChannel();
}
#MessagingGateway(defaultRequestChannel = "mqttOutboundChannel")
public interface Gateway {
void sendToMqtt(String data);
}
}
Update:
Not sure if this is the proper logging but it is what I get from adding:
logging.level.org.springframework.web=Debug
logging.level.org.hibernate=Error
to application.properties.
https://hastebin.com/cuvonufeco.hs
Use a Messaging Gateway or simply send a message to the channel.
EDIT
#SpringBootApplication
public class So47846492Application {
public static void main(String[] args) {
SpringApplication.run(So47846492Application.class, args).close();
}
#Bean
public ApplicationRunner runner(MyGate gate) {
return args -> {
gate.send("someTopic", "foo");
Thread.sleep(5_000);
};
}
#Bean
#ServiceActivator(inputChannel = "toMqtt")
public MqttPahoMessageHandler mqtt() {
MqttPahoMessageHandler handler = new MqttPahoMessageHandler("tcp://localhost:1883", "foo",
clientFactory());
handler.setDefaultTopic("myTopic");
handler.setQosExpressionString("1");
return handler;
}
#Bean
public MqttPahoClientFactory clientFactory() {
DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
factory.setUserName("guest");
factory.setPassword("guest");
return factory;
}
#Bean
public MqttPahoMessageDrivenChannelAdapter mqttIn() {
MqttPahoMessageDrivenChannelAdapter adapter =
new MqttPahoMessageDrivenChannelAdapter("tcp://localhost:1883", "bar", "someTopic");
adapter.setOutputChannelName("fromMqtt");
return adapter;
}
#ServiceActivator(inputChannel = "fromMqtt")
public void in(String in) {
System.out.println(in);
}
#MessagingGateway(defaultRequestChannel = "toMqtt")
public interface MyGate {
void send(#Header(MqttHeaders.TOPIC) String topic, String out);
}
}

Categories

Resources