How to register my custom MessageConverter to the SimpleMessageListenerContainerFactory? - spring

I have customer converter that implements the MessageConverter interface. However, I dont see a way to register it with the SimpleMessageListenerContainerFactory. As a result I get a error when I try to read a message from SQS that is in the source format as it doesnt know how to convert it to the target object.
I looked through the SqsConfiguration class and I see that the simpleMessageListenerContainer bean being defined has a queueMessageHandler set on it. The QueueMessageHandler has resolvers on it, one of which is a CompositeMessageConverter which takes a Collection of MessageConverter types. I am guessing somehow I need to add my custom MessageConverter to this collection. I cant seem to get a handle to how I can do that.
Can someone help help me point to a wayI can register my customer MessageMapper?

From what I can tell, the only way to really do this is to create your own QueueMessageHandlerFactory with whatever resolvers/converters you need.
For example, add this to your #Configuration class:
#Bean
public QueueMessageHandlerFactory queueMessageHandlerFactory() {
List<MessageConverter> converters = ...
CompositeMessageConverter converter = new CompositeMessageConverter(converters);
QueueMessageHandlerFactory factory = new QueueMessageHandlerFactory();
factory.setArgumentResolvers(Arrays.asList(new PayloadArgumentResolver(converter));
return factory;
}
SqsConfiguration should pick up your QueueMessageHandlerFactory bean so it won't create one itself.

The JMS message is converted firstly. After that, MessageListenerContainer transmit the message to the registered listener. So you need register your converter before MessageListenerContainer get it. There is only in JmsTemplate or JmsMessagingTemplate call the method setMessageConverter or setJmsMessageConverter to configure your converter.

Related

ObjectMapper configure GLOBAL LocalDateTime on Spring Java

I'm trying to achieve something absurdly simple, that is set the global time format to all json serialized in the spring boot application... I've tried many suggestions from other questions but it looks like jackson chooses to ignore whatever configuration i set to object mapper, my code is
#Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("dd-MM-yyyy hh:mm")));
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("dd-MM-yyyy hh:mm")));
mapper.registerModule(javaTimeModule);
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
mapper.configure(SerializationFeature.WRITE_DATES_WITH_CONTEXT_TIME_ZONE, false);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return mapper;
}
if I autowire the ObjectMapper and use it manually like System.out.println(objectMapper.writeValueAsString(LocalDateTime.now())); I get the dates in the format I want.
But all my controllers keep generating json in this format 2022-06-30T22:44:11
All my entities and Dtos use LocalDateTime as time object...
What am I missing to make it work?
Please dont suggest annotate all my LocalDateTime to set the pattern, I want a configuration that I can set globally
thanks
Try annotating objectMapper configuration with #Primary.
Also article bellow suggests some solutions too https://www.baeldung.com/spring-boot-customize-jackson-objectmapper. If #Primary doesn't work, check that out

Replacing micronaut's default KafkaProducerFactory with custom factory implementation

I need to customize the default KafkaProducerFactory (or any other default factory, say KafkaConsumerFactory) that ships with micronaut-kafka dependency. For that I tried to replace the existing factory using,
#Factory
#Replaces(factory = KafkaProducerFactory.class)
class CustomFactory extends KafkaProducerFactory {
#Bean
#Any
public <K, V> Producer<K, V> getProducer(
#Nullable InjectionPoint<KafkaProducer<K, V>> injectionPoint,
#Nullable #Parameter AbstractKafkaProducerConfiguration<K, V> producerConfiguration) {
validate(producerConfiguration); //this is my primary intension
super.getProducer(injectionPoint, producerConfiguration);
}
}
But it seems that Micronaut is not able to replace KafkaProducerFactory hence both the factory exists and I am getting error saying
"multiple candidate bean exists [CustomFactory, KafkaProducerFactory]"
I also thought to exclude the KafkaProducerFactory while the application loads, but could not find anything similar to Spring's ComponentScan.excludeFilter in Micronaut.
Is there anything wrong in my configuration or is there any other way to achieve the same?
Finally I got the answer. Let me elaborate little more for the actual context,
Problem
We have custom way of creating producer/consumer instances i.e. a custom class that creates those given the config properties. Now I had to modify the default factories so that instead of creating the instances on its own, the factory should invoke our custom class to instantiate producer/consumers.
Solution
I had to add #Primary along with other annotations and its working,
#Factory
#Replaces(factory = KafkaProducerFactory.class)
#Primary
class CustomFactory extends KafkaProducerFactory {
//code here
}
But the way I acheived this is kind of a work around because,
my primary intension was to override the producer/consumer creation part of default factory in a sub class and then replacing the default factory by the sub class. But as per the code structure in default factory class, it was not a single place where we can plug our custom code (no specific public method present consolidating the code for creating producer/consumer, it was being created from 3 separate places with "new") to create the producer. Hence we had to copy the entire default factory class and replaced the 3 places with custom code which does not seem to be a correct way.

RetryListenerSupport handlers executed on all #Retryables

I checked out docs and it seems (and is intuitive) that you have to register a RetryListenerSupport within the #Retryable annotation listener's arg.
But for some reason, the RetryListenerSupport gets executed on all #Retryables within the project, without adding it to any listeners args - is this the expected behaviour?
If yes, what's the listeners argument for at all?
Sorry for late answer but I just ran into the exact same issue.
I think the annotation was updated and the listener field was removed as it does not appear in the current documentation.
I implemented a new interceptor for my project and in order to log the retries, I implemented a RetryListener that was registered in a new RetryTemplate used inside the interceptor.
As an example is better than a long explanation, it looked like this:
#Configuration
public class MyRetryInterceptor{
#Bean
public RetryOperationsInterceptor retryInterceptorCustom() {
UniformRandomBackOffPolicy backOffPolicy = new BackOffPolicy(); //Configure it
RetryPolicy retryPolicyCustom = new RetryPolicy(); //Configure it
RetryTemplate template = RetryTemplate.builder()
.customBackoff(backOffPolicy)
.customPolicy(retryPolicyCustom )
.withListener(new MyRetryListener())
.build();
RetryOperationsInterceptor interceptor = new RetryOperationsInterceptor();
interceptor.setRetryOperations(template);
return interceptor;
}
}
Then you can use it inside a #Retryable annotation like this:
#Retryable(interceptor = "retryInterceptorCustom")
I know this is not perfect at all so use it at your own risk.

how to register custom converters for spring messaging, web sockets #DestinationVariable or jms #Header

I'm integrating spring web sockets capability into an existing spring mvc application, everything works as expected, except for enabling custom Spring Conversion on my inbound messages via #DestinationVariable.
Now I already have custom converters fully working for the http side, ex #RequestParam or #PathVariable but the same conversion on a websocket controller method throws a ConverterNotFoundException
Ex. I have a custom converter that converts String into Users
public class StringToUserConverter implements Converter<String,User>{
#Autowired UserDAO userDAO;
#Override
public User convert(String id) {
return userDAO.getUser(Integer.parseInt(id));
}
}
And this works exactly as expected in my http controllers, where I can pass in an id, and its automatically converted to the domain class
public String myControllerMethod(#RequestParam User user)
However the same does not work for my websocket controller for a parameter annotated with #DestinationVariable
#MessageMapping("/users/{user}")
#SendTo("/users/greetings")
public String send(#DestinationVariable User user) {
return "hello"
}
I stepped through the code and I can see that the DestinationVariableMethodArgumentResolver has the default conversion service which doesnt include my custom coverters
So how do I register custom converters, or a custom ConversionService so that it works for web sockets like it already does for http controllers
So now I'm running into the same issue with #Header annotation for JmsListener methods.
Same idea, #Header User user, throws the ConverterNotFound exception.
#JmsListener(destination = "testTopic")
public void testJmsListener(Message m, #Header User user)..
Here I was trying to pass the user id on the message header, and have spring convert it, but to no avail, only basic default conversions are supported, like strings or numbers.
I have stepped through quite a bit of initialization code in Spring here, and I can see that a new DefaultConversionService gets instantiated in many places, without any consideration for external configuration.
It looks like these modules are not nearly as mature as Spring MVC or the developers took a shortcut. But based on my inspection there is no way to easily configure custom converters.
Ok and here is the very hacky, not recommended, approach that did work. Its pretty convoluted and brittle, Im not going to use it, but just for illustration purposes here is what it took to register a custom converter for #Header jms mapping.
Here Im passing in a user_email on the jms message header, and wanted spring to automatically convert the id/email into the actual domain object User. I already had a working converter that does this well in mvc/http mode.
public class StringToUserConverter implements Converter<String,User>{
#Autowired
UserDAO userDAO;
public User convert(String email) {
return userDAO.getByEmail(email);
}
}
The above part is pretty standard and straight forward. Here comes the idiotically convoluted part. I stepped through the spring jms listener initialization code and found lowest spot where I could cut-in with my custom converter for jms #Header.
I created a service, that will #Autowire one of springs Jms beans, and then sets a custom conversion service on it using #PostConstruct. Even here some of the properties were private, so I had to use reflection to read them
#Service
public class JmsCustomeConverterSetter {
#Autowired
StringToUserConverter stringToUserConverter;
#Autowired
JmsListenerAnnotationBeanPostProcessor jmsPostProcessor;
#PostConstruct
public void attachCustomConverters() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
//create custom converter service that includes my custom converter
GenericConversionService converterService = new GenericConversionService();
converterService.addConverter(stringToUserConverter); //add custom converter, could add multiple here
DefaultConversionService.addDefaultConverters(converterService); //attach some default converters
//reflection to read the private field so i can use it later
Field field = jmsPostProcessor.getClass().getDeclaredField("beanFactory"); //NoSuchFieldException
field.setAccessible(true);
BeanFactory beanFactory = (BeanFactory) field.get(jmsPostProcessor); //IllegalAccessException
DefaultMessageHandlerMethodFactory f = new DefaultMessageHandlerMethodFactory();
f.setConversionService(converterService);
f.setBeanFactory(beanFactory); //set bean factory read using reflection
f.afterPropertiesSet();
jmsPostProcessor.setMessageHandlerMethodFactory(f);
}
}
Creating the DefaultMessageHandlerMethodFactory was based on code I saw in org.springframework.messaging.handler.annotation.support.MessageHandlerMethodFactory.
I would definitely not recommend using this in production. It is fairly brittle and unnecessarily complex.
Spring...sometimes it's a breath of fresh air... and sometimes it's convoluted-clap-trap

Wro4j: Accessing Spring #Service from custom post processor

I've successfully implemented a custom post processor filter with the help of the wro4j documentation.
Its job is to generate and prepend SASS vars to a group of SASS files which are then handed off to the rubySassCss filter for transpiling, and it's doing this job well.
The problem is that I wanted to hand the job of determining the SASS vars off to a custom ThemeManager #Service managed by Spring. I hadn't considered that the filter wouldn't be able to see the autowired #Service but that seems to be the case.
When I #Autowire the #Service into a controller, it works fine, but when I try the same thing with the filter I get a NPE when attempting to use it.
Is there a way to make the #Service visible to the filters or am I approaching this the wrong way?
Thanks for any help.
UPDATE:
It's taken some doing and attacking from a lot of angles, but I seem to be having success with autowiring my themeManagerService into the app configuration where I have my WRO filterRegistrationBean bean. I then pass the themeManagerService bean as a second argument to my custom ConfigurableWroManagerFactory.
Living in the custom WroManagerFactory is a reference to a custom UriLocator, which takes that themeManagerService as an argument. The custom UriLocator is invoked by a CSS resource containing an arbitrary keyword within a group.
The new UriLocator is able to generate a ByteArrayInputStream from what the themeManagerService provides it and pass it into the pipeline.
Simple.
I'll follow up when this approach pans/fizzles out.
In the end, I was able to provide the spring managed ThemeManagerService directly to the custom post processor, rather than relying on a custom UriLocator. I had tried that early on, but forgot to call super() in the new constructor, so the processor registration system was breaking.
I pass the #Autowired ThemeManagerService to my CustomConfigurableWroManagerFactory when registering the WRO bean:
#Autowired
ThemeManagerService themeManagerService;
#Bean
FilterRegistrationBean webResourceOptimizer(Environment env) {
FilterRegistrationBean fr = new FilterRegistrationBean();
ConfigurableWroFilter filter = new ConfigurableWroFilter();
Properties props = buildWroProperties(env);
filter.setProperties(props);
//The overridden constructor passes ThemeManager along
filter.setWroManagerFactory(new CustomConfigurableWroManagerFactory(props,themeManagerService));
filter.setProperties(props);
fr.setFilter(filter);
fr.addUrlPatterns("/wro/*");
return fr;
}
The constructor injection of ThemeManagerService into CustomConfigurableWroManagerFactory means it can be passed along to the custom postprocessor as it's registered by contributePostProcessors:
public class CustomConfigurableWroManagerFactory extends Wro4jCustomXmlModelManagerFactory {
private ThemeManagerService themeManagerService;
public CustomConfigurableWroManagerFactory(Properties props,ThemeManagerService themeManagerService) {
//forgetting to call super derailed me early on
super(props);
this.themeManagerService = themeManagerService;
}
#Override
protected void contributePostProcessors(Map<String, ResourcePostProcessor> map) {
//ThemeManagerService is provided as the custom processor is registered
map.put("repoPostProcessor", new RepoPostProcessor(themeManagerService));
}
}
Now, the post processor has access to ThemeManagerService:
#SupportedResourceType(ResourceType.CSS)
public class RepoPostProcessor implements ResourcePostProcessor {
private ThemeManagerService themeManagerService;
public RepoPostProcessor(ThemeManagerService themeManagerService) {
super();
this.themeManagerService = themeManagerService;
}
public void process(final Reader reader, final Writer writer) throws IOException {
String resourceText = "/* The custom PostProcessor fetched the following SASS vars from the ThemeManagerService: */\n\n";
resourceText += themeManagerService.getFormattedProperties();
writer.append(resourceText);
//read in the merged SCSS and add it after the custom content
writer.append(IOUtils.toString(reader));
reader.close();
writer.close();
}
}
This approach is working as expected/intended so far. Hope it comes in handy for someone else.
Wro4j is a great tool and much appreciated.

Resources