Spring Integration - #Filter discardChannel and/or throwExceptionOnRejection being ignored? - java-8

I have a java DSL based spring integration (spring-integration-java-dsl:1.0.1.RELEASE) flow which puts messages through a Filter to filter out certain messages. The Filter component works okay in terms of filtering out unwanted messages.
Now, I would like to set either a discardChannel="discard.ch" but, when I set the discard channel, the filtered out messages never seem to actually go to the specified discardChannel. Any ideas why this might be?
My #Filter annotated class/method:
#Component
public class MessageFilter {
#Filter(discardChannel = "discard.ch")
public boolean filter(String payload) {
// force all messages to be discarded to test discardChannel
return false;
}
}
My Integration Flow class:
#Configuration
#EnableIntegration
public class IntegrationConfig {
#Autowired
private MessageFilter messageFilter;
#Bean(name = "discard.ch")
public DirectChannel discardCh() {
return new DirectChannel();
}
#Bean
public IntegrationFlow inFlow() {
return IntegrationFlows
.from(Jms.messageDriverChannelAdapter(mlc)
.filter("#messageFilter.filter('payload')")
...
.get();
}
#Bean
public IntegrationFlow discardFlow() {
return IntegrationFlows
.from("discard.ch")
...
.get();
}
}
I have turned on spring debugging on and, I can't see where discarded messages are actually going. It is as though the discardChannel I have set on the #Filter is not being picked up at all. Any ideas why this might be?

The annotation configuration is for when using annotation-based configuration.
When using the dsl, the annotation is not relevant; you need to configure the .filter within the DSL itself...
.filter("#messageFilter.filter('payload')", e -> e.discardChannel(discardCh())

Related

How to create notes/description based on custom Annotation to Swagger

I have a Springboot Rest application where I have annotation which is automatically converted API's and Params.
I have custom annotation where i include some notes to it, How can i get that generated to my swagger page in OpenAPI 3?
Ex:
#RestController
Class Controller {
#GetMapping(/test/result/)
#CustomAnnotation(value = "This description should come in swagger")
void method() {
}
}
SpringDoc allows you to customize the generated OpenAPI specification by implementing your own Customizer bean.
There are plenty of Customizer interfaces that you can use for customization, but the most usable are OperationCustomizer, ParameterCustomizer, and PropertyCustomizer.
Below is an example of Operation Customizer for your use case.
#Component
public class OperationCustomizer implements org.springdoc.core.customizers.OperationCustomizer {
#Override
public Operation customize(Operation operation, HandlerMethod handlerMethod) {
CustomAnnotation annotation = handlerMethod.getMethodAnnotation(CustomAnnotation.class);
if (annotation != null) {
operation.description(annotation.value());
}
return operation;
}
}
Here you can find an example of the project that uses custom annotations and Customizers for them.
And here an example of the project that modifies the generated specification based on the #NonNull annotation.
As #VadymVL pointed out a component extending OperationCustomizer is necessary:
#Component
public class CustomOperationCustomizer implements OperationCustomizer {
#Override
public Operation customize(Operation operation, HandlerMethod handlerMethod) {
/* your code */
}
Just don't forget to register it:
#Bean
public GroupedOpenApi publicApi(CustomOperationCustomizer operationCustomizer) {
return GroupedOpenApi.builder()
.group(/*your group*/)
.pathsToMatch(/*your endpoint*/)
.
/* anything else you need */
.
.addOperationCustomizer(operationCustomizer)
.build();
}
And you're good to go!

Spring Kafka Requirements for Supporting Multiple Consumers

As one would expect its common to want to have different Consumers deserializing in different ways off topics in Kafka. There is a known problem with spring boot autoconfig. It seems that as soon as other factories are defined Spring Kafka or the autoconfig complains about not being able to find a suitable consumer factory anymore. Some have pointed out that one solution is to include a ConsumerFactory of type (Object, Object) in the config. But no one has shown the source code for this or clarified if it needs to be named in any particular way. Or if simply adding this Consumer to the config removes the need to turn off autoconfig. All that remains very unclear.
If you are not familiar with this issue please read https://github.com/spring-projects/spring-boot/issues/19221
Where it was just stated ok, define the ConsumerFactory and add it somewhere in your config. Can someone be a bit more precise about this please.
Show exactly how to define the ConsumerFactory so that Spring boot autoconfig will not complain.
Explain if turning off autoconfig is or is not needed?
Explain if Consumer Factory needs to be named in any special way or not.
The simplest solution is to stick with Boot's auto-configuration and override the deserializer on the #KafkaListener itself...
#SpringBootApplication
public class So63108344Application {
public static void main(String[] args) {
SpringApplication.run(So63108344Application.class, args);
}
#KafkaListener(id = "so63108344-1", topics = "so63108344-1")
public void listen1(String in) {
System.out.println(in);
}
#KafkaListener(id = "so63108344-2", topics = "so63108344-2", properties =
ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG +
"=org.apache.kafka.common.serialization.ByteArrayDeserializer")
public void listen2(byte[] in) {
System.out.println(in);
}
#Bean
public NewTopic topic1() {
return TopicBuilder.name("so63108344-1").partitions(1).replicas(1).build();
}
#Bean
public NewTopic topic2() {
return TopicBuilder.name("so63108344-2").partitions(1).replicas(1).build();
}
}
For more advanced container customization (or if you don't want to pollute the #KafkaListener, you can use a ContainerCustomizer...
#Component
class Customizer {
public Customizer(ConcurrentKafkaListenerContainerFactory<?, ?> factory) {
factory.setContainerCustomizer(container -> {
if (container.getGroupId().equals("so63108344-2")) {
container.getContainerProperties().setAckMode(AckMode.MANUAL_IMMEDIATE);
container.getContainerProperties().getKafkaConsumerProperties()
.setProperty(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.ByteArrayDeserializer");
}
});
}
}

Spring integration: discardChannel doesn't work for filter of integration flow

I faced with a problem when I create IntegrationFlow dynamically using DSL.
If discardChannel is defined as message channel object and if the filter returns false - nothing happens (the message is not sent to specified discard channel)
The source is:
#Autowired
#Qualifier("SIMPLE_CHANNEL")
private MessageChannel simpleChannel;
IntegrationFlow integrationFlow = IntegrationFlows.from("channelName")
.filter(simpleMessageSelectorImpl, e -> e.discardChannel(simpleChannel))
.get();
...
#Autowired
#Qualifier("SIMPLE_CHANNEL")
private MessageChannel simpleChannel;
#Bean
public IntegrationFlow simpleFlow() {
return IntegrationFlows.from(simpleChannel)
.handle(m -> System.out.println("Hello world"))
.get();
#Bean(name = "SIMPLE_CHANNEL")
public MessageChannel simpleChannel() {
return new DirectChannel();
}
But if the discard channel is defined as name of the channel, everything works.
Debuging I found that mentioned above the part of the code:
IntegrationFlow integrationFlow = IntegrationFlows.from("channelName")
.filter(simpleMessageSelectorImpl, e -> e.discardChannel(simpleChannel))
.get();
returns flow object which has map with integrationComponents and one of the component which is FilterEndpointSpec has "handler" field of type MessageFilter with discardChannel = null, and discardChannelName = null;
But if discard channel is defined as name of the channel the mentioned field "handler" with discardChannel=null but discardChannelName="SIMPLE_CHANNEL", as result everything works good.
It is behavior of my running application. Also I wrote the test and in test everything works good for both cases (the test doesn't run all spring context so maybe it is related to any conflict there)
Maybe someone has idea what it can be.
The spring boot version is 2.1.8.RELEASE, spring integration is 5.1.7.RELEASE
Thanks
The behaviour you describe is indeed incorrect and made me wonder, but after testing it out I can't seem to reproduce it, so perhaps there is something missing from the information you provided. In any event, here is the complete app that I've modeled after yours which works as expected. So perhaps you can compare and see if something jumps:
#SpringBootApplication
public class IntegrationBootApp {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(IntegrationBootApp.class, args);
MessageChannel channel = context.getBean("channelName", MessageChannel.class);
PollableChannel resultChannel = context.getBean("resultChannel", PollableChannel.class);
PollableChannel discardChannel = context.getBean("SIMPLE_CHANNEL", PollableChannel.class);
channel.send(MessageBuilder.withPayload("foo").build());
System.out.println("SUCCESS: " + resultChannel.receive());
channel.send(MessageBuilder.withPayload("bar").build());
System.out.println("DISCARD: " + discardChannel.receive());
}
#Autowired
#Qualifier("SIMPLE_CHANNEL")
private PollableChannel simpleChannel;
#Bean
public IntegrationFlow integrationFlow() {
IntegrationFlow integrationFlow = IntegrationFlows.from("channelName")
.filter(v -> v.equals("foo"), e -> e.discardChannel(simpleChannel))
.channel("resultChannel")
.get();
return integrationFlow;
}
#Bean(name = "SIMPLE_CHANNEL")
public PollableChannel simpleChannel() {
return new QueueChannel();
}
#Bean
public PollableChannel resultChannel() {
return new QueueChannel(10);
}
}
with output
SUCCESS: GenericMessage [payload=foo, headers={id=cf7e2ef1-e49d-1ecb-9c92-45224d0d91c1, timestamp=1576219339077}]
DISCARD: GenericMessage [payload=bar, headers={id=bf209500-c3cd-9a7c-0216-7d6f51cd5f40, timestamp=1576219339078}]

Bean injection for spring integration message handler

I am fairly new to spring and spring integration. What I'm trying to do: publish mqtt messages using spring integration.
Here is the code:
#Configuration
#IntegrationComponentScan
#Service
public class MQTTPublishAdapter {
private MqttConfiguration mqttConfiguration;
public MQTTPublishAdapter(MqttConfiguration mqttConfiguration) {
this.mqttConfiguration = mqttConfiguration;
}
#Bean
public MessageChannel mqttOutboundChannel() {
return new PublishSubscribeChannel();
}
#Bean
public MqttPahoClientFactory mqttClientFactory() {
DefaultMqttPahoClientFactory factory = new
DefaultMqttPahoClientFactory();
//... set factory details
return factory;
}
#Bean
#ServiceActivator(inputChannel = "mqttOutboundChannel")
public MQTTCustomMessageHandler mqttOutbound() {
String clientId = UUID.randomUUID().toString();
MQTTCustomMessageHandler messageHandler =
new MQTTCustomMessageHandler(clientId, mqttClientFactory());
//...set messagehandler details
return messageHandler;
}
//I extend this only because the publish method is protected and I want to
send messages to different topics
public class MQTTCustomMessageHandler extends MqttPahoMessageHandler {
//default constructors
public void sendMessage(String topic, String message){
MqttMessage mqttMessage = new MqttMessage();
mqttMessage.setPayload(message.getBytes());
try {
super.publish(topic, mqttMessage, null);
} catch (Exception e) {
log.error("Failure to publish message on topic " + topic,
e.getMessage());
}
}
}
This is the clase where I am trying to inject the Handler
#Service
public class MQTTMessagePublisher {
private MQTTCustomMessageHandler mqttCustomMessageHandler;
public MQTTMessagePublisher(#Lazy MQTTCustomMessageHandler
mqttCustomMessageHandler) {
this.mqttCustomMessageHandler = mqttCustomMessageHandler;
}
public void publishMessage(String topic, String message) {
mqttCustomMessageHandler.sendMessage(topic, message);
}
}
So my question is about how should I inject the bean I am trying to use because if I remove the #Lazy annotation it says that "Requested bean is currently in creation: Is there an unresolvable circular reference?". I do not have any circular dependencies as in the bean I only set some strings, so I'm guessing that I don't really understand how this should work.
Very sorry about the formating, it's one of my first questions around here.
Edit:
If I remove
#ServiceActivator(inputChannel = "mqttOutboundChannel")
and add
messageHandler.setChannelResolver((name) -> mqttOutboundChannel());
it works. I'm still unclear why the code crashes.
You show a lot of custom code, but not all of them.
It's really hard to answer to questions where it is only a custom code. Would be great to share as much info as possible. For example an external project on GitHub to let us to play and reproduce would be fully helpful and would save some time.
Nevertheless, I wonder what is your MQTTCustomMessageHandler. However I guess it is not a MessageHandler implementation. From here the #ServiceActivator annotation is not going to work properly since it is applied really for the mqttOutbound(), not whatever you expect. Or you need to move this annotation to your sendMessage() method in the MQTTCustomMessageHandler or have it as a MessageHandler.
On the other hand it is not clear why do you need that #ServiceActivator annotation at all since you call that method manually from the MQTTMessagePublisher.
Also it is not clear why you have so much custom code when Framework provides for your out-of-the-box channel adapter implementations.
Too many questions to your code, than possible answer...
See more info in the reference manual:
https://docs.spring.io/spring-integration/docs/current/reference/html/#annotations
https://docs.spring.io/spring-integration/docs/current/reference/html/#mqtt

Write out Spring Boot metrics to Stdout or aggregator?

I have a java application written on top of Spring Boot and I can see the metrics being generated through the /metrics management API. I would like to filter the metrics that are being generated (based on metric prefix) and print to stdout OR send the selected metrics to a 3rd party aggregator (not the ones referenced here)
I tried the code suggested by this answer but it didn't result in any metrics being written to the stdout. This is what I added to my Application.java class:
#Bean
#ServiceActivator(inputChannel = "metricsChannel")
public MessageHandler metricsHandler() {
return System.out::println;
}
What is the best way to intercept the metrics on a preconfigured cadence so I can process and write them to stdout or publish them to an aggregator?
Thanks.
Looks like it's a bug in the Spring Boot: https://github.com/spring-projects/spring-boot/issues/5517.
We have to declare something like this ourselves as a workaround:
#Bean
public MessageChannel metricsChannel() {
return new DirectChannel();
}
#Bean
#ExportMetricWriter
public MessageChannelMetricWriter messageChannelMetricWriter() {
return new MessageChannelMetricWriter(metricsChannel());
}
#Bean
#ServiceActivator(inputChannel = "metricsChannel")
public MessageHandler metricsHandler() {
return System.out::println;
}

Resources