Override fasterxml jackson ObjectMapper used by Spring Integration dsl - spring

Using Transformers.toJson() my json date looks like this:
"createdDate":{"year":2017,"month":"OCTOBER","monthValue":10,"dayOfMonth":25,"hour":7,"minute":57,"second":36,"nano":972000000,"dayOfWeek":"WEDNESDAY","dayOfYear":298,"chronology":{"calendarType":"iso8601","id":"ISO"}}
Here is the outbound ampq configuration:
#Bean
public IntegrationFlow outboundCdrRabbitFlowDefinition() {
return IntegrationFlows.from(CHANNEL_NAME)
.transform(Transformers.toJson())
.handle(Amqp.outboundAdapter(new RabbitTemplate(cachingConnectionFactory))
.routingKey("routing-key"))
.get();
}
The consumer of the rabbit queue expects the format "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
Is there any way i can override the default ObjectMapper used by spring integration?
For example i have the this configuration in my web api config that extends WebMvcConfigurerAdapter:
#Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.setTimeZone(UTC_TIME_ZONE);
mapper.setDateFormat(ISO_8601_DATE_FORMAT);
mapper.registerModule(new Jdk8Module());
mapper.registerModule(new JavaTimeModule());
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return mapper;
}
#Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
mappingJackson2HttpMessageConverter.setObjectMapper(objectMapper());
return mappingJackson2HttpMessageConverter;
}
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(mappingJackson2HttpMessageConverter());
super.configureMessageConverters(converters);
}
Is there some way to reuse the objectMapper bean in Spring Integration so the mapping configuration is the same across all my outbound endpoints, web api http or integration ampq?

There is an overloaded version of the Transformers.toJson():
Transformers.toJson(new Jackson2JsonObjectMapper(objectMapper))

Related

Using ContextResolver to register ObjectMapper in runtime

We're using JAX-RS (Jersey implementation) to call to external systems.
On JAX-RS Client's creation I'm registering the below context resolver to use custom ObjectMapper:
public class JacksonObjectMapperProvider implements ContextResolver<ObjectMapper>
{
#Override
public ObjectMapper getContext(Class<?> type)
{
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true);
mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
mapper.configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, false);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.configure(SerializationFeature.WRITE_NULL_MAP_VALUES, false);
mapper.configure(SerializationFeature.WRITE_EMPTY_JSON_ARRAYS, true);
mapper.setSerializationInclusion(Include.NON_NULL);
return mapper;
}
}
But I don't want to have the ObjectMapper defined in JacksonObjectMapperProvider. I want JacksonObjectMapperProvider to be able to retrieve it in runtime from somewhere, or have someone set the ObjectMapper on JacksonObjectMapperProvider.
I cannot do something like bellow, because the ObjectMapper is defined on some instance that creating the jax-rs Client. And here I don't have a reference to that instance:
public class JacksonObjectMapperProvider implements ContextResolver<ObjectMapper>
{
#Override
public ObjectMapper getContext(Class<?> type)
{
return someService.getObjectMapper();
}
}
Is there another way to do it?
Is there a way to pass data to JacksonObjectMapperProvider when registering on Client?
The solution is easier then I thought, instead of registering the class:
ClientConfig clConfig = new ClientConfig();
client.register(JacksonObjectMapperProvider.class);
as I did, you can register an instance of the class, and on instance creation pass whatever you want:
ClientConfig clConfig = new ClientConfig();
client.register(new JacksonObjectMapperProvider(objectMapper));
The updated provider:
public class JacksonObjectMapperProvider implements ContextResolver<ObjectMapper>
{
private ObjectMapper mapper;
public JacksonObjectMapperProvider(ObjectMapper mapper)
{
this.mapper = mapper;
}
#Override
public ObjectMapper getContext(Class<?> type)
{
return mapper;
}
}

JmsMessagingTemplate is message converter broken?

I was trying to use a JmsMessagingTemplate with a JmsTemplate that has a org.springframework.jms.support.converter.MappingJackson2MessageConverter but the problem is that navigating up on the JmsMessagingTemplate to org.springframework.messaging.core.AbstractMessageSendingTemplate where the converter used is an implementation of org.springframework.messaging.converter.MessageConverter which doesn't seem compatible with org.springframework.jms.support.converter.MappingJackson2MessageConverter.
Is this broken or I'm trying to do something wrong here?
You haven't added your configuration code. So assuming that you have not added bean for message converter. Please find below code snippet for the configuration, hope that will resolve the problem.
#Bean
public MessageConverter jacksonJmsMessageConverter() {
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
converter.setTargetType(MessageType.TEXT);
converter.setTypeIdPropertyName("_type");
converter.setObjectMapper(objectMapper());
return converter;
}
#Bean
public ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
return objectMapper;
}
#Bean
public JmsTemplate jmsTemplate(ConnectionFactory connectionFactory,
DefaultJmsListenerContainerFactoryConfigurer configurer) {
JmsTemplate jmsTemplate = new JmsTemplate();
jmsTemplate.setConnectionFactory(connectionFactory());
jmsTemplate.setMessageConverter(jacksonJmsMessageConverter());
return jmsTemplate;
}
#Bean
public ConnectionFactory connectionFactory() {
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory();
connectionFactory.setBrokerURL(url);
connectionFactory.setUserName(user);
connectionFactory.setPassword(password);
return connectionFactory;
}
Since I'm wrapping around a jmsMessagingTemplate I had to set the converter explicitly like this:
public DelegatingJmsMessagingTemplate(JmsTemplate jmsTemplate) {
this.jmsMessagingTemplate = new JmsMessagingTemplate(jmsTemplate);
final var messagingMessageConverter = new MessagingMessageConverter(jmsTemplate.getMessageConverter());
this.jmsMessagingTemplate.setJmsMessageConverter(messagingMessageConverter); //seems to do the trick
this.jmsMessagingTemplate.setDefaultDestinationName(jmsTemplate.getDefaultDestinationName());
}
now both publisher and subscriber convert the message accordingly.
As I mention in a comment, I found out that org.springframework.jms.core.JmsMessagingTemplate have a org.springframework.jms.core.JmsMessagingTemplate.MessagingMessageCreator where the real conversion happens.

Override JSON properties in #RestController in Spring 4.3.3 WebMVC

I want to override the following properties in Spring RestController in WebMVC.
objectMapper.setSerializationInclusion(Include.NON_NULL);
objectMapper.configure(SerializationFeature.WRAP_ROOT_VALUE, true);
objectMapper.setDateFormat(DATEFORMAT);
I've tried the following in multiple combinations, but nothing worked. Still getting the null value without root name in the response.
#EnableWebMvc
public class ApplicationContextConfig extends WebMvcConfigurerAdapter {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
//MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.setSerializationInclusion(Include.NON_NULL);
objectMapper.configure(SerializationFeature.WRAP_ROOT_VALUE, true);
objectMapper.setDateFormat(DATEFORMAT);
//jsonConverter.setObjectMapper(objectMapper);
MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter(objectMapper);
//jsonConverter.setJsonPrefix(jsonPrefix);
converters.add(jsonConverter);
}
#Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
for (HttpMessageConverter<?> converter : converters) {
if (converter instanceof MappingJackson2HttpMessageConverter) {
MappingJackson2HttpMessageConverter jsonMessageConverter = (MappingJackson2HttpMessageConverter) converter;
ObjectMapper objectMapper = jsonMessageConverter.getObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.setSerializationInclusion(Include.NON_NULL);
objectMapper.configure(SerializationFeature.WRAP_ROOT_VALUE, true);
objectMapper.setDateFormat(DATEFORMAT);
break;
}
}
}
#Bean
#Primary
public ObjectMapper getObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setSerializationInclusion(Include.NON_NULL);
objectMapper.configure(SerializationFeature.WRAP_ROOT_VALUE, true);
objectMapper.setDateFormat(DATEFORMAT);
return objectMapper;
}
}
Reponse:
{
"Message": null,
"Admin": null,
"Company": null
}
You should "substitute" the object mapper bean provided by spring with your own and configure it.
#Configuration
public class MyJacksonCustomConfiguration {
#Bean
public ObjectMapper objectMapper() { // note the name should be 'objectMapper'
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setSerializationInclusion(Include.NON_NULL);
objectMapper.configure(SerializationFeature.WRAP_ROOT_VALUE, true);
objectMapper.setDateFormat(DATEFORMAT);
return objectMapper;
}
}
All in all, make sure that only one bean of type object mapper should exist in the application context. Apart of fact that its a pretty "heavy" object, if spring will inject a wrong mapper you won't be able to benefit from customization that you've done.

converting activemq msg in spring boot

I am using spring boot to decode the jms message
configuration
#Bean
public JmsListenerContainerFactory<?> myFactory(ConnectionFactory connectionFactory,
DefaultJmsListenerContainerFactoryConfigurer configurer) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
// This provides all boot's default to this factory, including the message
// converter
configurer.configure(factory, connectionFactory);
factory.setMessageConverter(this.jacksonJmsMessageConverter());
// You could still override some of Boot's default if necessary.
return factory;
}
#Bean // Serialize message content to json using TextMessage
public MessageConverter jacksonJmsMessageConverter() {
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
converter.setTargetType(MessageType.TEXT);
converter.setObjectMapper(new ObjectMapper());
return converter;
}
and I am able to receive the message, and decode the message
#JmsListener(destination = "myQueue", containerFactory = "myFactory")
public void receiveMessage(ActiveMQTextMessage msg) throws JMSException, IOException {
String text = msg.getText();
ObjectMapper mapper = new ObjectMapper();
MyObject obj = mapper.readValue(text, MyObject.class);
my problem is I don't want to call mapper.readValue(xxx) everytime, I prefer
to configure the mapper and conversion in the messageconverter bean, I the key
is to call the msg.getText() from the converter, but how do I get a reference of
of the msg in the converter, or there are smarter way to do it.
Spring do that for you since you have configured the MessageConverter of the factory by factory.setMessageConverter(this.jacksonJmsMessageConverter());
so use the argument like this :
#JmsListener(destination = "myQueue", containerFactory = "myFactory")
public void receiveMessage(MyObject obj) throws JMSException, IOException {
//do stuff with obj
}

Spring Hibernate Jackson Hibernate5Module

I have set up spring 4.3.1 with Hibernate 5.1.0 and Jackson 2.7.5
I had some lazy init Exceptions because the Jackson ObjectMapper tries to convert my Objects to late when I am out of the Transactional Service.
Therefore I have read the Hibernate5Module for Jackson.
After adding the Module I do not get lazy Exceptions BUT all #JsonView Annotations are ignored and my lazy collections are 'null'
public class SpringWebConfig extends WebMvcConfigurerAdapter {
...
#Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
for (HttpMessageConverter<?> converter : converters) {
if (converter instanceof org.springframework.http.converter.json.MappingJackson2HttpMessageConverter) {
ObjectMapper mapper = ((MappingJackson2HttpMessageConverter) converter).getObjectMapper();
mapper.registerModule(new Hibernate5Module());
}
}
}
}
Is there anything I am doing wrong?
The Hibernate5Module should initialize the lazy collections ...
By creating your own ObjectMapper, you're overriding the one Spring Boot would set up, which would include a bunch of useful modules, such as Jdk8 module.
What you should do instead, is just add the Hibernate5() module to the Application Context and Spring Boot will automatically add it to the ObjectMapper that it sets up. Like this in any #Configuration class:
#Bean
public Hibernate5Module hibernate5Module() {
return new Hibernate5Module();
}
Got it to work with the following
#EnableWebMvc
#Configuration
#ComponentScan({ "..." })
public class SpringWebConfig extends WebMvcConfigurerAdapter {
#Autowired
SessionFactory sf;
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
Hibernate5Module module = new Hibernate5Module(sf);
module.disable(Feature.USE_TRANSIENT_ANNOTATION);
module.enable(Feature.FORCE_LAZY_LOADING);
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.modulesToInstall(module);
converters.add(new MappingJackson2HttpMessageConverter(builder.build()));
}
}
I manage to make it work with the below implementation
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
Hibernate5Module module = new Hibernate5Module(); // or Hibernate4Module ... depends on hibernate version you are using
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(module);
converters.add(new MappingJackson2HttpMessageConverter(mapper));
}
jackson-datatype-hibernate5 bring many solutions but there are some default configurations as well.
Please have a look on
Below is the configuration I did as per my project requirements.
#Bean
public Hibernate5Module hibernateModule() {
Hibernate5Module module = new Hibernate5Module();
module.disable(Hibernate5Module.Feature.USE_TRANSIENT_ANNOTATION);
module.enable(Hibernate5Module.Feature.FORCE_LAZY_LOADING);
return module;
}

Resources