I'm facing an issue after adding Hibernate4Module to support lazy-objects serialization.
My configuration file:
#EnableWebMvc
#ServletComponentScan(basePackages = "my.backend")
#EnableAutoConfiguration(exclude = MultipartAutoConfiguration.class)
#Configuration
#EnableSwagger2
public class MyConfiguration extends WebMvcConfigurerAdapter{
#Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(false).
favorParameter(true).
parameterName("mediaType").
ignoreAcceptHeader(true).
useJaf(false).
defaultContentType(MediaType.APPLICATION_JSON).
mediaType("xml", MediaType.APPLICATION_XML).
mediaType("json", MediaType.APPLICATION_JSON);
}
/* Here we register the Hibernate4Module into an ObjectMapper, then set this custom-configured ObjectMapper
* to the MessageConverter and return it to be added to the HttpMessageConverters of our application*/
public MappingJackson2HttpMessageConverter jacksonMessageConverter(){
MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
ObjectMapper current = messageConverter.getObjectMapper();
//Registering Hibernate4Module to support lazy objects
current.registerModule(new Hibernate4Module());
messageConverter.setObjectMapper(current);
return messageConverter;
}
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
//Here we add our custom-configured HttpMessageConverter
converters.add(jacksonMessageConverter());
super.configureMessageConverters(converters);
}
}
Hibernate entities get serializied ok.
The problem is with plain jsons, that where stored in DB or generated in any other way.
For example
String myJson = {"myField":"2123456"}
Is returned in mailformed format
"{\"myField\":"2123456"}
Looks like some default modules get broken. Can anyone give a piece of advice?
Related
I am trying to send an event using RabbitMQ and SpringBoot.
#Configuration class:
#Bean
public Jackson2JsonMessageConverter producerJackson2MessageConverter() {
return new Jackson2JsonMessageConverter();
}
Event class:
public class TheEvent{
#JsonFormat(pattern = "dd::MM::yyyy")
private LocalDateTime date;
//setters getters
}
When I send it, it arrives as:
{"month":"JULY","year":2018,"dayOfMonth":12,"dayOfWeek":"THURSDAY","dayOfYear":193,"hour":16,"minute":29,"nano":835000000,"second":24,"monthValue":7,"chronology":{"id":"ISO","calendarType":"iso8601"}},"direction":1}"
How can I serialize this date object in predefined pattern? (Remember that I just registering bean Jackson2JsonMessageConverter)
Also tried this:
#Bean
#Primary
public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) {
ObjectMapper objectMapper = builder.createXmlMapper(false).build();
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, true);
return objectMapper;
}
It works for me:
#Bean
public Jackson2JsonMessageConverter converter(Jackson2ObjectMapperBuilder builder) {
ObjectMapper objectMapper = builder.createXmlMapper(false).build();
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, true);
return new Jackson2JsonMessageConverter(objectMapper);
}
In project I used RabbitListener and that Jackson2JsonMessageConverter
Listener:
#RabbitListener(queues = "${spring.rabbitmq.queue}")
#Transactional
public void receiveSocialPost(SocialPost socialPost) {
}
We have spring boot with elasticsearch and mysql. We have a feature for reindexing all data from the mysql into elasticsearch, which is simple as:
#Service
#Transactional
public class SearchIndexer {
public void reindex(){
elasticsearchRepository.save(jpaRepository.findAll());
}
}
Now we have an entity called invoice, which has a lazy loaded collection with a "derived" calculation:
#Entity
#Table(name = "invoice")
#Document(indexName = "invoice")
public class Invoice implements Serializable {
//... other props
#OneToMany(fetch = FetchType.LAZY, mappedBy = "invoice")
#JsonIgnore
private Set<InvoiceItem> invoiceItems = new LinkedHashSet<>();
// getter and setters for invoiceItems
public boolean isAllSimple() {
if(getInvoiceType()==null){
return false;
}
if(getInvoiceItems()==null){
return false;
}
for(InvoiceItem item : getInvoiceItems()){
if(!item.isSimple()){
return false;
}
}
return true;
}
}
When the rest-controller is used, the resulting json contains correctly a property "allSimple". This is, because we run that with hibernate5module in one transaction.
However, when we call elasticsearchRepository.save(jpaRepository.findAll()) (also in a transaction), the objectmapper for elasticsearch cannot serialize the "allSimple" property, beacause of a LazyInitializationException. The elasticsearch-objectmapper is configured as follows:
#Bean
public ElasticsearchTemplate elasticsearchTemplate(Client client, Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder, Hibernate5Module hibernate5Module) {
return new ElasticsearchTemplate(client, new CustomEntityMapper(jackson2ObjectMapperBuilder.createXmlMapper(false).modulesToInstall(hibernate5Module).build()));
}
public class CustomEntityMapper implements EntityMapper {
private ObjectMapper objectMapper;
public CustomEntityMapper(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
objectMapper.configure( DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.configure( DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
}
#Override
public String mapToString(Object object) throws IOException {
return objectMapper.writeValueAsString(object);
}
#Override
public <T> T mapToObject(String source, Class<T> clazz) throws IOException {
return objectMapper.readValue(source, clazz);
}
}
The hibernate5module is loaded and registered, but did not solve the problem.
Normally we would add a "JsonIgnore" to that property, but we need that value, so this is no option.
Any ideas?!
I had a project configurated with this.
#EnableWebMvc
#Configuration
#ComponentScan(basePackages = "com.sagasoftware.tracker.*")
public class WebConfiguration extends WebMvcConfigurerAdapter {
#Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
ObjectMapper objectMapper = new ObjectMapper();
Hibernate5Module hibernate5Module = new Hibernate5Module();
objectMapper.registerModule(hibernate5Module);
objectMapper.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
messageConverter.setObjectMapper(objectMapper);
return messageConverter;
}
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(mappingJackson2HttpMessageConverter());
super.configureMessageConverters(converters);
}
}
If you are using spring boot, declaring the bean MappingJackson2HttpMessageConverter and registering the hibernate5module should fix your problem.
I could render a entity throught a rest controller.
Let me thank you in advance for your help!
I have a weird behaviour in an spring boot application. Let me explain it for you:
I'm wrapping some legacy web services (custom xml messages) with some nice rest-json services (via spring-mvc and spring boot and using jackson for serializing stuff)
In order to communicate with the legacy systems, I have created a custom XmlMapper, serializers and deserializers.
And finally, I have created an httpclientconfig, in order to define some http connection properties...
But after starting the app and trying to visit any endpoint (actuator ones for example), the app only returns xml. Event swagger endpoints return xml (what makes swagger-ui going nuts.
These are some of the classes:
#Configuration
public class HttpClientConfig {
private static final Logger logger = LoggerFactory.getLogger(HttpClientConfig.class);
#Value(value = "${app.http.client.max_total_connections}")
public String MAX_TOTAL_CONNECTIONS;
#Value(value = "${app.http.client.max_connections_per_route}")
public String MAX_CONNECTIONS_PER_ROUTE;
#Value(value = "${app.http.client.connection_timeout_milliseconds}")
public String CONNECTION_TIMEOUT_MILLISECONDS;
#Bean
public ClientHttpRequestFactory httpRequestFactory() {
return new HttpComponentsClientHttpRequestFactory(httpClient());
}
#Autowired
private XmlMapper xmlMapper;
#Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate(httpRequestFactory());
List<HttpMessageConverter<?>> converters = restTemplate.getMessageConverters();
for (HttpMessageConverter<?> converter : converters) {
if (converter instanceof MappingJackson2HttpMessageConverter) {
MappingJackson2HttpMessageConverter jsonConverter = (MappingJackson2HttpMessageConverter) converter;
jsonConverter.setObjectMapper(new ObjectMapper());
}
if (converter instanceof MappingJackson2XmlHttpMessageConverter) {
MappingJackson2XmlHttpMessageConverter jsonConverter = (MappingJackson2XmlHttpMessageConverter) converter;
jsonConverter.setObjectMapper(xmlMapper);
}
}
logger.debug("restTemplate object created====================================");
return restTemplate;
}
#Bean
public HttpClient httpClient() {
HttpClient httpClient = null;
try {
HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
// disable SSL check
SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
#Override
public boolean isTrusted(java.security.cert.X509Certificate[] arg0, String arg1)
throws CertificateException {
return true;
}
}).build();
httpClientBuilder.setSSLContext(sslContext);
// don't check Hostnames
HostnameVerifier hostnameVerifier = NoopHostnameVerifier.INSTANCE;
SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslContext, hostnameVerifier);
Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.getSocketFactory())
.register("https", sslSocketFactory).build();
PoolingHttpClientConnectionManager connMgr = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
connMgr.setMaxTotal(Integer.parseInt(MAX_TOTAL_CONNECTIONS));
connMgr.setDefaultMaxPerRoute(Integer.parseInt(MAX_CONNECTIONS_PER_ROUTE));
RequestConfig config = RequestConfig.custom()
.setConnectTimeout(Integer.parseInt(CONNECTION_TIMEOUT_MILLISECONDS)).build();
httpClientBuilder.setDefaultRequestConfig(config);
httpClientBuilder.setConnectionManager(connMgr);
// to avoid nohttpresponse
httpClientBuilder.setRetryHandler(new HttpRequestRetryHandler() {
#Override
public boolean retryRequest(IOException exception, int executionCount,
org.apache.http.protocol.HttpContext context) {
// TODO Auto-generated method stub
return true;
}
});
httpClient = httpClientBuilder.build();
} catch (Exception e) {
logger.error("Excption creating HttpClient: ", e);
}
return httpClient;
}
}
And the xml mapper
#Configuration
public class XmlMapperConfig{
#Bean
public XmlMapper getXmlMapper() {
XmlMapper mapper=new XmlMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(CafRequestObject.class, new CafRequestObjectSerializer());
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.registerModule(module);
mapper.findAndRegisterModules();
CafXmlSerializationProvider cafXmlProvider=new CafXmlSerializationProvider(new XmlRootNameLookup());
mapper.setSerializerProvider(cafXmlProvider);
return mapper;
}
}
I call to findAndregisterModules, because I am also developing some libraries which provides additional serializers for services (modularized stuff)
I'm completely lost with this. Any help would be much appreciated...
Regards!
I have solved it extending WebMvcConfigurerAdapter:
#Configuration
#EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
#Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.defaultContentType(MediaType.APPLICATION_JSON);
}
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry
.addResourceHandler("swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry
.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
}
}
Thanks again!
Is it possible to register the jackson-datatype-jdk8 module in the Restlet org.restlet.ext.jackson extension package? I need to take advantage of the new Optional feature. My guess is that it should be accessible through the converter services (getConverterService()) but I can't find anything in the documentation that suggests exactly how setting a module is possible.
I eventually pieced together from a variety of sources an answer that works with Restlet 2.3. My guess is that this will be refactored for Restlet 3 and it won't work for versions < 2.3 so be aware that this solution will most likely have a limited shelf life.
First step is to create a custom Jackson converter that implements any custom requirements you have:
public class CustomJacksonConverter extends JacksonConverter {
#Override
protected <T> JacksonRepresentation<T> create(MediaType mediaType, T source) {
ObjectMapper mapper = createMapper();
JacksonRepresentation<T> jr = new JacksonRepresentation<T>(mediaType, source);
jr.setObjectMapper(mapper);
return jr;
}
#Override
protected <T> JacksonRepresentation<T> create(Representation source, Class<T> objectClass) {
ObjectMapper mapper = createMapper();
JacksonRepresentation<T> jr = new JacksonRepresentation<T>(source, objectClass);
jr.setObjectMapper(mapper);
return jr;
}
private ObjectMapper createMapper() {
JsonFactory jsonFactory = new JsonFactory();
jsonFactory.configure(JsonGenerator.Feature.AUTO_CLOSE_TARGET, false);
ObjectMapper mapper = new ObjectMapper(jsonFactory);
mapper.registerModule(new Jdk8Module());
return mapper;
}
}
You then need to create a way of replacing the default Jackson converter with a copy of your new one:
static void replaceConverter(
Class<? extends ConverterHelper> converterClass,
ConverterHelper newConverter) {
ConverterHelper oldConverter = null;
List<ConverterHelper> converters = Engine.getInstance().getRegisteredConverters();
for (ConverterHelper converter : converters) {
if (converter.getClass().equals(converterClass)) {
converters.remove(converter);
oldConverter = converter;
break;
}
}
converters.add(newConverter);
}
You can now replace the converter in your inbound root:
replaceConverter(JacksonConverter.class, new CustomJacksonConverter());
Great start by #tarka, but gave me stream errors; i propose some [lower impact] refinements:
public class CustomJacksonConverter extends JacksonConverter {
#Override
protected <T> JacksonRepresentation<T> create(MediaType mediaType, T source) {
return new CustomJacksonRepresentation<>(mediaType, source);
}
#Override
protected <T> JacksonRepresentation<T> create(Representation source, Class<T> objectClass) {
return new CustomJacksonRepresentation<>(source, objectClass);
}
}
and then...
public class CustomJacksonRepresentation<T> extends JacksonRepresentation<T> {
public CustomJacksonRepresentation(MediaType mediaType, T object) {
super(mediaType, object);
}
public CustomJacksonRepresentation(Representation representation, Class<T> objectClass) {
super(representation, objectClass);
}
public CustomJacksonRepresentation(T object) {
super(object);
}
#Override
protected ObjectMapper createObjectMapper() {
ObjectMapper mapper = super.createObjectMapper();
mapper.registerModule(new Jdk8Module());
return mapper;
}
}
and replace the converter in the same way #tarka does
Every object with Date format is being serialized as a long.
I've read around that I need to create a custom object mapper
and so I did:
public class CustomObjectMapper extends ObjectMapper {
public CustomObjectMapper() {
super();
configure(Feature.WRITE_DATES_AS_TIMESTAMPS, false);
setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
}
}
I've also registered that custom mapper as a converter
#Override
protected void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(converter());
addDefaultHttpMessageConverters(converters);
}
#Bean
MappingJacksonHttpMessageConverter converter() {
MappingJacksonHttpMessageConverter converter = new MappingJacksonHttpMessageConverter();
converter.setObjectMapper(new CustomObjectMapper());
return converter;
}
but still, it doesn't work, and I recieve a long as a date.
Any idea what am I doing wrong?
You'll need to implement your own Dateserializer, just like the following (got it from this tutorial, so props to Loiane, not me ;-) ):
package ....util.json;
import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.map.JsonSerializer;
import org.codehaus.jackson.map.SerializerProvider;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
#Component
public class JsonDateSerializer extends JsonSerializer<Date>{
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("dd.MM.yyyy HH:mm "); // change according to your needs
#Override
public void serialize(Date date, JsonGenerator gen, SerializerProvider provider)
throws IOException {
String formattedDate = dateFormat.format(date);
gen.writeString(formattedDate);
}
}
then you could just add the following annotation to your Date-Objects and it will persist fine:
#JsonSerialize(using = JsonDateSerializer.class)
public Date getCreated() {
return created;
}
At least it works with spring 3.2.4 and jackson 1.9.13 here.
edit: Think about using FastDateFormat instead of SimpleDateFormat, for it's the threadsafe-alternative (as mentioned in the comments of Loianes article)
Try adding 0 as index in #add()
#Configuration
#ComponentScan()
#EnableWebMvc
#PropertySource("classpath:/web.properties")
public class WebConfig extends WebMvcConfigurerAdapter
{
#Override
public void configureMessageConverters(final List<HttpMessageConverter<?>> converters)
{
converters.add(0, jsonConverter());
}
#Bean
public MappingJacksonHttpMessageConverter jsonConverter()
{
final MappingJacksonHttpMessageConverter converter = new MappingJacksonHttpMessageConverter();
converter.setObjectMapper(new CustomObjectMapper());
return converter;
}
}
It worked for me.