Spring Data: Saving LocalTime to MongoDB using CustomConversions - spring-boot

How do I only convert the string in HH:mm format to LocalTime and the other way?
Here is my mongo config and right now it is converting any fields that are of String type to LocalTime and failed in the process because some of them are not meant to be LocalTime... but some of them are LocalTime in String format, so those actually need to get converted to LocalTime
#Bean
public MappingMongoConverter mappingMongoConverter() throws Exception {
MappingMongoConverter converter = new MappingMongoConverter(NoOpDbRefResolver.INSTANCE, this.mongoMappingContext());
converter.setCustomConversions(this.customConversions());
converter.afterPropertiesSet();
return converter;
}
#Bean
public MongoMappingContext mongoMappingContext() {
MongoMappingContext context = new MongoMappingContext();
context.setSimpleTypeHolder(new SimpleTypeHolder(new HashSet<>(Arrays.asList(
LocalTime.class
)), MongoSimpleTypes.HOLDER));
return context;
}
#Bean
public MongoCustomConversions customConversions() {
List<Converter<?, ?>> converters = new ArrayList<>();
converters.add(new LocalTimeToStringConverter());
converters.add(new StringToLocalTimeConverter());
return new MongoCustomConversions(converters);
}

Related

JwtAuthenticationToken is not in the allowlist, Jackson issue

I have created my authorization server using org.springframework.security:spring-security-oauth2-authorization-server:0.2.2 and my client using org.springframework.boot:spring-boot-starter-oauth2-client. The users are able to sign in and out successfully, however, while testing I noticed that if I log in successfully then restart the client (but not the server) without signing out and try to login in again the server throws the following error in an endless loop of redirects
java.lang.IllegalArgumentException: The class with org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken and name of org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken is not in the allowlist. If you believe this class is safe to deserialize, please provide an explicit mapping using Jackson annotations or by providing a Mixin. If the serialization is only done by a trusted source, you can also enable default typing. See https://github.com/spring-projects/spring-security/issues/4370 for details
I tried to follow this link https://github.com/spring-projects/spring-security/issues/4370 but the solution on it did not work for me. I also tried a different solution described in this link https://github.com/spring-projects/spring-authorization-server/issues/397#issuecomment-900148920 and modified my authorization server code as follows:-
Here is my Jackson Configs
#Configuration
public class JacksonConfiguration {
/**
* Support for Java date and time API.
*
* #return the corresponding Jackson module.
*/
#Bean
public JavaTimeModule javaTimeModule() {
return new JavaTimeModule();
}
#Bean
public Jdk8Module jdk8TimeModule() {
return new Jdk8Module();
}
/*
* Support for Hibernate types in Jackson.
*/
#Bean
public Hibernate5Module hibernate5Module() {
return new Hibernate5Module();
}
/*
* Module for serialization/deserialization of RFC7807 Problem.
*/
#Bean
public ProblemModule problemModule() {
return new ProblemModule();
}
/*
* Module for serialization/deserialization of ConstraintViolationProblem.
*/
#Bean
public ConstraintViolationProblemModule constraintViolationProblemModule() {
return new ConstraintViolationProblemModule();
}
/**
* To (de)serialize a BadCredentialsException, use CoreJackson2Module:
*/
#Bean
public CoreJackson2Module coreJackson2Module() {
return new CoreJackson2Module();
}
#Bean
#Primary
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(coreJackson2Module());
mapper.registerModule(javaTimeModule());
mapper.registerModule(jdk8TimeModule());
mapper.registerModule(hibernate5Module());
mapper.registerModule(problemModule());
mapper.registerModule(constraintViolationProblemModule());
return mapper;
}
}
and here is my Authorization server config
#Configuration(proxyBeanMethods = false)
public class AuthServerConfig {
private final DataSource dataSource;
private final AuthProperties authProps;
private final PasswordEncoder encoder;
public AuthServerConfig(DataSource dataSource, AuthProperties authProps, PasswordEncoder encoder) {
this.dataSource = dataSource;
this.authProps = authProps;
this.encoder = encoder;
}
#Bean
public JdbcTemplate jdbcTemplate() {
return new JdbcTemplate(dataSource);
}
#Bean
#Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityFilterChain authServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer<>();
authorizationServerConfigurer.tokenRevocationEndpoint(tokenRevocationEndpoint -> tokenRevocationEndpoint
.revocationResponseHandler((request, response, authentication) -> {
Assert.notNull(request, "HttpServletRequest required");
HttpSession session = request.getSession(false);
if (!Objects.isNull(session)) {
session.removeAttribute("SPRING_SECURITY_CONTEXT");
session.invalidate();
}
SecurityContextHolder.getContext().setAuthentication(null);
SecurityContextHolder.clearContext();
response.setStatus(HttpStatus.OK.value());
})
);
RequestMatcher endpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher();
http
.requestMatcher(endpointsMatcher)
.authorizeRequests(authorizeRequests -> authorizeRequests.anyRequest().authenticated())
.csrf(csrf -> csrf.ignoringRequestMatchers(endpointsMatcher))
.apply(authorizationServerConfigurer);
return http.formLogin(Customizer.withDefaults()).build();
}
#Bean
public RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate, TokenSettings tokenSettings) {
JdbcRegisteredClientRepository clientRepository = new JdbcRegisteredClientRepository(jdbcTemplate);
RegisteredClient webClient = RegisteredClient.withId("98a9104c-a9c7-4d7c-ad03-ec61bcfeab36")
.clientId(authProps.getClientId())
.clientName(authProps.getClientName())
.clientSecret(encoder.encode(authProps.getClientSecret()))
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.redirectUri("http://127.0.0.1:8000/login/oauth2/code/web-client")
.scope(OidcScopes.OPENID)
.scope(OidcScopes.PROFILE)
.tokenSettings(tokenSettings)
.build();
clientRepository.save(webClient);
return clientRepository;
}
#Bean
public OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate,
RegisteredClientRepository registeredClientRepository,
ObjectMapper objectMapper) {
JdbcOAuth2AuthorizationService authorizationService =
new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);
JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper rowMapper = new JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper(registeredClientRepository);
ClassLoader classLoader = JdbcOAuth2AuthorizationService.class.getClassLoader();
objectMapper.registerModules(SecurityJackson2Modules.getModules(classLoader));
objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module());
// You will need to write the Mixin for your class so Jackson can marshall it.
// objectMapper.addMixIn(UserPrincipal .class, UserPrincipalMixin.class);
rowMapper.setObjectMapper(objectMapper);
authorizationService.setAuthorizationRowMapper(rowMapper);
return authorizationService;
}
#Bean
public OAuth2AuthorizationConsentService authorizationConsentService(JdbcTemplate jdbcTemplate,
RegisteredClientRepository registeredClientRepository) {
return new JdbcOAuth2AuthorizationConsentService(jdbcTemplate, registeredClientRepository);
}
#Bean
public JWKSource<SecurityContext> jwkSource() {
RSAKey rsaKey = generateRsa();
JWKSet jwkSet = new JWKSet(rsaKey);
return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
}
private static RSAKey generateRsa() {
KeyPair keyPair = generateRsaKey();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
return new RSAKey.Builder(publicKey)
.privateKey(privateKey)
.keyID(UUID.randomUUID().toString())
.build();
}
private static KeyPair generateRsaKey() {
KeyPair keyPair;
try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
keyPair = keyPairGenerator.generateKeyPair();
} catch (Exception ex) {
throw new IllegalStateException(ex);
}
return keyPair;
}
#Bean
public ProviderSettings providerSettings() {
return ProviderSettings.builder()
.issuer(authProps.getIssuerUri())
.build();
}
#Bean
public TokenSettings tokenSettings() {
return TokenSettings.builder()
.accessTokenTimeToLive(Duration.ofDays(1))
.refreshTokenTimeToLive(Duration.ofDays(1))
.build();
}
}
But am still facing the same issue.
How do I solve this? Any assistance is highly appreciated.
After trying out different solutions this was how I was able to solve it.
I changed my OAuth2AuthorizationService bean to look like this.
#Bean
public OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate,
RegisteredClientRepository registeredClientRepository) {
JdbcOAuth2AuthorizationService authorizationService =
new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);
JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper rowMapper =
new JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper(registeredClientRepository);
JdbcOAuth2AuthorizationService.OAuth2AuthorizationParametersMapper oAuth2AuthorizationParametersMapper =
new JdbcOAuth2AuthorizationService.OAuth2AuthorizationParametersMapper();
ObjectMapper objectMapper = new ObjectMapper();
ClassLoader classLoader = JdbcOAuth2AuthorizationService.class.getClassLoader();
List<Module> securityModules = SecurityJackson2Modules.getModules(classLoader);
objectMapper.registerModules(securityModules);
objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module());
objectMapper.addMixIn(JwtAuthenticationToken.class, JwtAuthenticationTokenMixin.class);
rowMapper.setObjectMapper(objectMapper);
oAuth2AuthorizationParametersMapper.setObjectMapper(objectMapper);
authorizationService.setAuthorizationRowMapper(rowMapper);
authorizationService.setAuthorizationParametersMapper(oAuth2AuthorizationParametersMapper);
return authorizationService;
}
and here is my JwtAuthenticationTokenMixin configurations
#JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
#JsonDeserialize(using = JwtAuthenticationTokenDeserializer.class)
#JsonAutoDetect(
fieldVisibility = JsonAutoDetect.Visibility.ANY,
getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
#JsonIgnoreProperties(ignoreUnknown = true)
public abstract class JwtAuthenticationTokenMixin {}
class JwtAuthenticationTokenDeserializer extends JsonDeserializer<JwtAuthenticationToken> {
#Override
public JwtAuthenticationToken deserialize(JsonParser parser, DeserializationContext context) throws IOException {
ObjectMapper mapper = (ObjectMapper) parser.getCodec();
JsonNode root = mapper.readTree(parser);
return deserialize(parser, mapper, root);
}
private JwtAuthenticationToken deserialize(JsonParser parser, ObjectMapper mapper, JsonNode root)
throws JsonParseException {
JsonNode principal = JsonNodeUtils.findObjectNode(root, "principal");
if (!Objects.isNull(principal)) {
String tokenValue = principal.get("tokenValue").textValue();
long issuedAt = principal.get("issuedAt").longValue();
long expiresAt = principal.get("expiresAt").longValue();
Map<String, Object> headers = JsonNodeUtils.findValue(
principal, "headers", JsonNodeUtils.STRING_OBJECT_MAP, mapper);
Map<String, Object> claims = new HashMap<>();
claims.put("claims", principal.get("claims"));
Jwt jwt = new Jwt(tokenValue, Instant.ofEpochMilli(issuedAt), Instant.ofEpochMilli(expiresAt), headers, claims);
return new JwtAuthenticationToken(jwt);
}
return null;
}
}
abstract class JsonNodeUtils {
static final TypeReference<Set<String>> STRING_SET = new TypeReference<Set<String>>() {
};
static final TypeReference<Map<String, Object>> STRING_OBJECT_MAP = new TypeReference<Map<String, Object>>() {
};
static String findStringValue(JsonNode jsonNode, String fieldName) {
if (jsonNode == null) {
return null;
}
JsonNode value = jsonNode.findValue(fieldName);
return (value != null && value.isTextual()) ? value.asText() : null;
}
static <T> T findValue(JsonNode jsonNode, String fieldName, TypeReference<T> valueTypeReference,
ObjectMapper mapper) {
if (jsonNode == null) {
return null;
}
JsonNode value = jsonNode.findValue(fieldName);
return (value != null && value.isContainerNode()) ? mapper.convertValue(value, valueTypeReference) : null;
}
static JsonNode findObjectNode(JsonNode jsonNode, String fieldName) {
if (jsonNode == null) {
return null;
}
JsonNode value = jsonNode.findValue(fieldName);
return (value != null && value.isObject()) ? value : null;
}
}
you don't need to create a Mixin, because it's all ready created by authorization springboot module. juste
#Bean
public OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) {
JdbcOAuth2AuthorizationService authorizationService = new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);
JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper rowMapper = new JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper(registeredClientRepository);
ClassLoader classLoader = JdbcOAuth2AuthorizationService.class.getClassLoader();
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModules(new CoreJackson2Module());
objectMapper.registerModules(SecurityJackson2Modules.getModules(classLoader));
objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module());
rowMapper.setObjectMapper(objectMapper);
authorizationService.setAuthorizationRowMapper(rowMapper);
return authorizationService;
}
i think you miss this line and is where the token mixin is registered
objectMapper.registerModules(new CoreJackson2Module());

Spring MVC 5 Rest API convert XML to Bean using JAXB

We are migrating Jersey Endpoints to Spring MVC Rest endpoints. These endpoints consumes and produces XML. But Spring MVC is unable to convert the XML input bean because XML tag and Bean's property name is different. I have to take request as String and Convert it using JAXB explicitly. Is there any better way to do that?
Web Configuration
#EnableWebMvc
#Configuration
public class WebConfig implements WebMvcConfigurer {
public static final String JAXB_PACKAGE = "com.comp.platform.dataholder";
#Autowired
private Jackson2ObjectMapperBuilder builder;
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new StringHttpMessageConverter());
converters.add(new FormHttpMessageConverter());
converters.add(createXmlHttpMessageConverter());
converters.add(createMappingJacksonHttpMessageConverter(builder));
// converters.add(createMappingJackson2XmlHttpMessageConverter(xmlMapper));
Jaxb2RootElementHttpMessageConverter jaxb2RootElementHttpMessageConverter = new Jaxb2RootElementHttpMessageConverter();
jaxb2RootElementHttpMessageConverter.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_XML));
converters.add(jaxb2RootElementHttpMessageConverter);
}
#Bean
public Jackson2ObjectMapperBuilder customJson() {
return new Jackson2ObjectMapperBuilder().indentOutput(true).serializationInclusion(JsonInclude.Include.NON_NULL)
.propertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE).failOnUnknownProperties(false);
}
#Bean
#Primary
public ObjectMapper createObjectMapper(Jackson2ObjectMapperBuilder builder) {
ObjectMapper mapper = builder.build();
mapper.setSerializationInclusion(Include.NON_NULL);
return mapper;
}
#Bean
public Jaxb2Marshaller jaxb2Marshaller() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setClassesToBeBound(NonFinancialDataHolder.class, JaxbDataHolder.class, ViestimateDataHolder.class,CTRequest.class,com.app.rest.PolicyDetailRequest.class,com.app.rest.TransactionSubmitRequest.class,com.app.rest.AgentSuppressInfoRequest.class);
return marshaller;
}
/**
* #return
*/
#Bean
public MappingJackson2HttpMessageConverter createMappingJacksonHttpMessageConverter(
Jackson2ObjectMapperBuilder builder) {
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setObjectMapper(builder.build());
return converter;
}
#Autowired
Jaxb2Marshaller jaxb2Marshaller;
private HttpMessageConverter<Object> createXmlHttpMessageConverter() {
Assert.notNull(jaxb2Marshaller, "Jaxb Marshaller dependency required");
MarshallingHttpMessageConverter xmlConverter = new MarshallingHttpMessageConverter(jaxb2Marshaller);
xmlConverter.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_XML));
return xmlConverter;
}
public JAXBContext getMarshaller() throws JAXBException {
return JAXBContext.newInstance(JAXB_PACKAGE);
}
}
Bean
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "", propOrder = {
"_UserId",
"_Password",
"_RequestDateTime",
"_Policy"
})
#XmlRootElement(name = "PolicyDetailRequest")
public class PolicyDetailRequest
{
#XmlElement(name = "UserId")
protected String _UserId;
#XmlElement(name = "Password")
protected String _Password;
#XmlElement(name = "RequestDateTime")
protected String _RequestDateTime;
#XmlElement(name = "Policy")
protected String _Policy;
}
Rest Endpoint
#Slf4j
#RestController
#RequestMapping("/api")
public class api
{
#Autowired
Jaxb2Marshaller jaxb2Marshaller;
#PostMapping(value="/PolicyResponse",produces=MediaType.APPLICATION_XML_VALUE,consumes = MediaType.APPLICATION_XML_VALUE)
#ResponseBody
public PolicyDetailResponse getPolicyResponse(#RequestBody String data)
{
PolicyDetailRequest request = (PolicyDetailRequest) jaxb2Marshaller.unmarshal(new StreamSource(new StringReader(data)));
PolicyDetailResponse response = new PolicyDetailResponse();
InternetRSHandler handler = new InternetRSHandler();
response= handler.getPolicyRSResponse(request);
return response;
}
}
Input Request
<PolicyDetailRequest>
<UserId>1234567890</UserId>
<Password>password</Password>
<RequestDateTime>20200420-15:07:23</RequestDateTime>
<Policy>A76ABCD12</Policy>
</PolicyDetailRequest>

InvalidFormatException for Date - fixing without using JsonFormat or modifying original class

Introduction
We are using a custom starter hosted on a nexus repository, that contains spring-cloud-feign clients that make requests to microservices.
One of the microservices returns the dates as "dd-MM-yyyy HH:mm:ssZ" and this works in most of our applications. However, we have one application that is throwing the following error:
Caused by: com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `java.util.Date` from String "2019-10-16 14:23:17": not a valid representation (error: Failed to parse Date value '2019-10-16 14:23:17': Unparseable date: "2019-10-16 14:23:1
7")
Current work-around
My Current work-around, as I don't want to pollute the starter, is to extend the class and create a local feign-client and local pojo with the proper JsonFormat:
public class DocumentMetaDataFix extends DocumentMetaData {
#JsonFormat(
shape = Shape.STRING,
pattern = "yyyy-MM-dd HH:mm:ss"
)
private Date creationDate;
#JsonFormat(
shape = Shape.STRING,
pattern = "yyyy-MM-dd HH:mm:ss"
)
Failed Fixes
I have tried the following in my configuration class, in order to try affecting the de-serialization from another path. However, the DocumentMetaDataSerializer is never called. The ObjectMapper bean IS called.
#Configuration
#EnableSpringDataWebSupport
#RequiredArgsConstructor
public class MyConfig extends WebMvcConfigurerAdapter {
#Bean
public Jackson2ObjectMapperBuilderCustomizer addCustomBigDecimalDeserialization() {
return new Jackson2ObjectMapperBuilderCustomizer() {
#Override
public void customize(Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder) {
jacksonObjectMapperBuilder.deserializerByType(DocumentMetaData.class, new DocumentMetaDataDeserializer());
}
};
}
#Primary
#Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, true);
mapper.setDateFormat(new SimpleDateFormat("dd-MM-yyyy HH:mm:ss"));
//mapper.configure(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS, true);
return mapper;
}
#Bean
public Module dynamoDemoEntityDeserializer() {
SimpleModule module = new SimpleModule();
module.addDeserializer(DocumentMetaData.class, new DocumentMetaDataDeserializer());
return module;
}
public static class DocumentMetaDataDeserializer extends JsonDeserializer<DocumentMetaData> {
#Override
public DocumentMetaData deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
// return DynamoDemoEntity instance;
JsonNode node = jp.getCodec().readTree(jp);
return null;
}
public DocumentMetaData deserializeWithType(JsonParser jp, DeserializationContext ctxt, TypeDeserializer t) throws IOException {
JsonNode node = jp.getCodec().readTree(jp);
return null;
}
}
Full Stacktrace
Caused by: com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `java.util.Date` from String "2019-10-16 14:23:17": not a valid representation (error: Failed to parse Date value '2019-10-16 14:23:17': Unparseable date: "2019-10-16 14:23:1
7")
at [Source: (ByteArrayInputStream); line: 1, column: 580] (through reference chain: eu.europa.ec.nova.documentstore.DocumentMetaData["creationDate"])
at com.fasterxml.jackson.databind.exc.InvalidFormatException.from(InvalidFormatException.java:67)
at com.fasterxml.jackson.databind.DeserializationContext.weirdStringException(DeserializationContext.java:1548)
at com.fasterxml.jackson.databind.DeserializationContext.handleWeirdStringValue(DeserializationContext.java:910)
at com.fasterxml.jackson.databind.deser.std.StdDeserializer._parseDate(StdDeserializer.java:524)
at com.fasterxml.jackson.databind.deser.std.StdDeserializer._parseDate(StdDeserializer.java:467)
at com.fasterxml.jackson.databind.deser.std.DateDeserializers$DateBasedDeserializer._parseDate(DateDeserializers.java:195)
at com.fasterxml.jackson.databind.deser.std.DateDeserializers$DateDeserializer.deserialize(DateDeserializers.java:285)
at com.fasterxml.jackson.databind.deser.std.DateDeserializers$DateDeserializer.deserialize(DateDeserializers.java:268)
at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:127)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:288)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:151)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4013)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3084)
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:237)
... 70 common frames omitted
So, any ideas?
I have searched through the project for references to Jackson in case there is anything else in my project causing this.
I am will try to go inside the ObjectMapper and try to debug the current parameters/fields of the configuration at ObjectMapper.java:3084 from the stacktace:
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3084)
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:237)
... 67 common frames omitted
Update
I added a breakpoint in the objectmapper constructor, and am seeing that it is being initialized from more than one location. This led me to suspect that spring-boot is not using my ObjectMapper. Instead it is using an internal spring one that is called from MappingJackson2HttpMessageConverter .
<init>:480, ObjectMapper
build:606, Jackson2ObjectMapperBuilder
<init>:59, MappingJackson2HttpMessageConverter
<init>:74, AllEncompassingFormHttpMessageConverter
I will therefore try to over-ride this internal spring one, based on results I found from: How to customise the Jackson JSON mapper implicitly used by Spring Boot?
However this also failed.
References
Is it possible to configure Jackson custom deserializers at class level for different data types?
https://docs.spring.io/spring-boot/docs/current/reference/html/howto-spring-mvc.html#howto-customize-the-jackson-objectmapper
https://www.baeldung.com/jackson-deserialization
very useful: https://mostafa-asg.github.io/post/customize-json-xml-spring-mvc-output/
How to customise Jackson in Spring Boot 1.4
Update - final list of tries
It still fails with an error.
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.serializationInclusion(JsonInclude.Include.NON_NULL);
builder.propertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);
builder.serializationInclusion(Include.NON_EMPTY);
builder.indentOutput(true).dateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
converters.add(new MappingJackson2HttpMessageConverter(builder.build()));
converters.add(new MappingJackson2XmlHttpMessageConverter(builder.createXmlMapper(true).build()));
//converters.add(cmsaMessageConverter());
converters.add(new StringHttpMessageConverter());
converters.add(new FormHttpMessageConverter());
converters.add(new MappingJackson2HttpMessageConverter());
}
#Bean
public Jackson2ObjectMapperBuilderCustomizer addCustomBigDecimalDeserialization() {
return new Jackson2ObjectMapperBuilderCustomizer() {
#Override
public void customize(Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder) {
jacksonObjectMapperBuilder.deserializerByType(DocumentMetaData.class, new DocumentMetaDataDeserializer());
}
};
}
#Primary
#Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, true);
mapper.setDateFormat(new SimpleDateFormat("dd-MM-yyyy HH:mm:ss"));
//mapper.configure(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS, true);
return mapper;
}
#Bean
public Module dynamoDemoEntityDeserializer() {
SimpleModule module = new SimpleModule();
module.addDeserializer(DocumentMetaData.class, new DocumentMetaDataDeserializer());
return module;
}
public static class DocumentMetaDataDeserializer extends JsonDeserializer<DocumentMetaData> {
#Override
public DocumentMetaData deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
// return DynamoDemoEntity instance;
JsonNode node = jp.getCodec().readTree(jp);
return null;
}
public DocumentMetaData deserializeWithType(JsonParser jp, DeserializationContext ctxt, TypeDeserializer t) throws IOException {
JsonNode node = jp.getCodec().readTree(jp);
return null;
}
}
It still fails with an error.
Try using LocalDateTime,
this is what I'm doing and working for me
#JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime date;

Spring boot JMS using different messages class

I'm using spring boot.
I want to use different models at both sender and receiver so that they don't depend to the same model (receiver doesn't need to add model of sender to classpath).
1) Should I do that?
2) And how can I do that?
Sender:
AccountEvent accountEvent = new com.dspc.account.domain.dto.AccountEvent(createdAccount.getId(), EventType.CREATED);
jmsMessagingTemplate.convertAndSend(new ActiveMQTopic("VirtualTopic.ACCOUNT-EVENT-TOPIC"), accountEvent);
Receiver:
#JmsListener(destination = "Consumer.AgentGenerator.VirtualTopic.ACCOUNT-EVENT-TOPIC")
public void receive(com.dspc.devicemgmt.domain.dto.AccountEvent accountEvent) {
System.out.println(accountEvent);
}
JMS config of both sender and receiver:
#Bean // Serialize message content to json using TextMessage
public MessageConverter jacksonJmsMessageConverter() {
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
converter.setTargetType(MessageType.TEXT);
converter.setTypeIdPropertyName("_type");
return converter;
}
Get exception when receiving message:
[com.dspc.account.domain.dto.AccountEvent]; nested exception is
java.lang.ClassNotFoundException:
com.dspc.account.domain.dto.AccountEvent
Note that there are 2 different packages:
- com.dspc.account.domain.dto.AccountEvent
- com.dspc.devicemgmt.domain.dto.AccountEvent
I'm thinking about creating a common message model. How do you think?
public class DspcCommonMessage {
private Map<String, String> properties;
private Optional<byte[]> payLoad = Optional.empty();
public Map<String, String> getProperties() {
return properties;
}
public void setProperties(Map<String, String> properties) {
this.properties = properties;
}
public Optional<byte[]> getPayLoad() {
return payLoad;
}
public void setPayLoad(Optional<byte[]> payLoad) {
this.payLoad = payLoad;
}
}
Sender and receiver:
public void publishMessage(com.dspc.account.domain.dto.AccountEvent accountEvent) {
ObjectMapper objectMapper = new ObjectMapper();
String messageAsString = objectMapper.writeValueAsString(accountEvent);
DspcCommonMessage dspcMessage = new DspcCommonMessage();
dspcMessage.setPayLoad(Optional.of(messageAsString.getBytes()));
jmsMessagingTemplate.convertAndSend(new ActiveMQTopic("VirtualTopic.ACCOUNT-EVENT-TOPIC"), dspcMessage);
}
#JmsListener(destination = "Consumer.AgentGenerator.VirtualTopic.ACCOUNT-EVENT-TOPIC")
public void receive(com.dspc.common.domain.DspcCommonMessage dspcCommonMessage) {
String jsonBody = new String(dspcCommonMessage.getPayload());
ObjectMapper objectMapper = new ObjectMapper();
com.dspc.devicemgmt.domain.dto.AccountEvent accountEvent = objectMapper.readValue(jsonBody,
com.dspc.devicemgmt.domain.dto.AccountEvent accountEvent.class);
System.out.println(accountEvent);
}

Spring Mongo converters not registred

I've registered a set of Mongo converters (including Jsr310Converters) but it does not convert LocalDate to Date (since canConvertFromLocalDateToDate is equal to false)
#Configuration
public class MongoConfig {
#Autowired
MongoDbFactory mongoDbFactory;
#Bean
public MongoTemplate mongoTemplate() throws Exception {
MappingMongoConverter converter = new MappingMongoConverter(new DefaultDbRefResolver(mongoDbFactory),
new MongoMappingContext());
converter.setMapKeyDotReplacement("_");
List<Converter<?, ?>> converters = new ArrayList<Converter<?, ?>>();
converters.add(new GeoPointConverter());
converters.addAll(Jsr310Converters.getConvertersToRegister());
converters.addAll(JodaTimeConverters.getConvertersToRegister());
converters.addAll(ThreeTenBackPortConverters.getConvertersToRegister());
converter.setCustomConversions(new CustomConversions(CustomConversions.StoreConversions.NONE, converters));
//is FALSE !
boolean canConvertFromLocalDateToDate = converter.getConversionService().canConvert(LocalDate.class, Date.class);
MongoTemplate mongoTemplate = new MongoTemplate(mongoDbFactory, converter);
return mongoTemplate;
}
When I try to store some entities with LocalDate fields I get an exception:
org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [java.time.LocalDate] to type [java.util.Date]
In fact I found that I had 2 instances of MappingMongoConverter at runtime. So I just reused the instance created by spring instead of instantiating a new one:
#Autowired
MappingMongoConverter converter;
The full snippet is:
#Configuration
public class MongoConfig {
#Autowired
MappingMongoConverter converter;
#Bean
public MongoTemplate mongoTemplate() throws Exception {
// NO NEED TO INSTANTIATE NEW MappingMongoConverter
//MappingMongoConverter converter = new MappingMongoConverter(new DefaultDbRefResolver(mongoDbFactory), new MongoMappingContext());
converter.setMapKeyDotReplacement("_");
List<Converter<?, ?>> converters = new ArrayList<Converter<?, ?>>();
converters.add(new GeoPointConverter());
converters.addAll(Jsr310Converters.getConvertersToRegister());
converters.addAll(JodaTimeConverters.getConvertersToRegister());
converters.addAll(ThreeTenBackPortConverters.getConvertersToRegister());
converter.setCustomConversions(new CustomConversions(CustomConversions.StoreConversions.NONE, converters));
//is FALSE !
boolean canConvertFromLocalDateToDate = converter.getConversionService().canConvert(LocalDate.class, Date.class);
converter.afterPropertiesSet();
MongoTemplate mongoTemplate = new MongoTemplate(mongoDbFactory, converter);
return mongoTemplate;
}

Resources