Spring Cloud Stream dynamic destinations Avro with the native encoding is not working - avro

I have been trying to use dynamic destinations feature of Spring Cloud Stream to publish a message in Avro format. However, due to the fact that I am using native encoding (Confluent Avro serializer), the message converter cannot handle this scenario. Obviously, when I was using static destination I was able to manage the native encoding by using use-native-encoding: true parameter at the "bindings" level. However, with the dynamic destination, it seems I don't have such an ability.
private boolean publishMessage(byte[] record, String target, String contentType, Schema schema) {
return resolver.resolveDestination(target)
.send(MessageBuilder
.createMessage(record, new MessageHeaders(
Collections.singletonMap(MessageHeaders.CONTENT_TYPE, contentType))));
}
If I use the following method with content-type of "application/*+avro" with the record in byte [] format, the following exception is thrown:
error occurred in message handler [org.springframework.cloud.stream.binder.kafka.KafkaMessageChannelBinder$ProducerConfigurationMessageHandler#5c778504]; nested exception is org.apache.kafka.common.errors.SerializationException: Error retrieving Avro schema: \"bytes\"
This exception normally happens if you miss the native encoding property.
If I try to deserialize the byte array to a generic record before publishing the message with the following method, then it is not able to find a proper message converter for it.
public static GenericRecord bytesToGenericAvro(byte[] bytes, Schema schema) {
DatumReader<GenericRecord>
datumReader = new GenericDatumReader<>(schema);
GenericRecord record = null;
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
bais.reset();
BinaryDecoder binaryDecoder = DecoderFactory.get().binaryDecoder(bais, null);
try {
record = datumReader.read(null, binaryDecoder);
} catch (IOException e) {
log.error("Unable to deserialize byte array to avro generic record", e.getMessage());
} finally {
try {
bais.close();
} catch (IOException e) {
log.warn("Unable to close ByteArrayInputStream", e.getMessage());
}
}
return record;
}
Update:
After adding this bean still facing the same issue. An exception is thrown while Spring Cloud Stream tries to convert message to Avro!
#Bean
public NewDestinationBindingCallback<KafkaProducerProperties> dynamicBindingConfigurer() {
return ((channelName, channel, producerProperties, extendedProducerProperties) -> {
producerProperties.setUseNativeEncoding(true);
producerProperties.setErrorChannelEnabled(true);
producerProperties.setPartitionCount(3);
});
}
Exception:
failed to send Message to channel 'output1'; nested exception is java.lang.IllegalStateException: Failed to convert message: 'GenericMessage [payload={...}, headers={contentType=application/*+avro, id=c22bf171-c6ae-cedb-b0be-3aa0fcbdf762, timestamp=1567053746112}]' to outbound message.
at org.springframework.cloud.stream.binding.MessageConverterConfigurer$OutboundContentTypeConvertingInterceptor.doPreSend(MessageConverterConfigurer.java:388)
at org.springframework.cloud.stream.binding.MessageConverterConfigurer$AbstractContentTypeInterceptor.preSend(MessageConverterConfigurer.java:422)
at org.springframework.integration.channel.AbstractMessageChannel$ChannelInterceptorList.preSend(AbstractMessageChannel.java:608)
at org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:443)
at org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:401)
at com.example.controller.PublisherController.publishMessage(PublisherController.java:90)
at com.example.controller.PublisherController.replayRecord(PublisherController.java:72)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:104)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:892)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:797)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1039)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:942)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1005)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:897)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:634)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:882)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:741)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:118)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:92)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:118)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:93)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:118)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:118)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:490)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:853)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1587)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:748)

You can modify the binding properties of a dynamic binding adding a NewDestinationBindingCallback bean and pass it into the resolver. See the documentation.
If the channel names are known in advance, you can configure the producer properties as with any other destination. Alternatively, if you register a NewDestinationBindingCallback<> bean, it is invoked just before the binding is created. The callback takes the generic type of the extended producer properties used by the binder. It has one method:
void configure(String channelName, MessageChannel channel, ProducerProperties producerProperties,
T extendedProducerProperties);
The following example shows how to use the RabbitMQ binder:
#Bean
public NewDestinationBindingCallback<RabbitProducerProperties> dynamicConfigurer() {
return (name, channel, props, extended) -> {
props.setRequiredGroups("bindThisQueue");
extended.setQueueNameGroupOnly(true);
extended.setAutoBindDlq(true);
extended.setDeadLetterQueueName("myDLQ");
};
}
If you need to support dynamic destinations with multiple binder types, use Object for the generic type and cast the extended argument as needed.
EDIT
It's a bug in the resolver; the callback isn't called to update the properties until after the channel is created and configured. It works fine for most properties, but not this one.
Here is a work-around:
#SpringBootApplication
#EnableBinding(Sink.class)
public class So57688303Application {
public static void main(String[] args) {
SpringApplication.run(So57688303Application.class, args);
}
#Bean
public NewDestinationBindingCallback<KafkaProducerProperties> dynamicBindingConfigurer() {
return ((channelName, channel, producerProperties, extendedProducerProperties) -> {
producerProperties.setUseNativeEncoding(true);
producerProperties.setErrorChannelEnabled(true);
producerProperties.setPartitionCount(3);
extendedProducerProperties.getConfiguration().put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,
MySerializer.class.getName());
});
}
#Bean
public ApplicationRunner runner(BinderAwareChannelResolver resolver) {
return args -> {
MessageChannel channel = resolver.resolveDestination("dynamic");
((AbstractMessageChannel) channel).removeInterceptor(0); // only need to do this on the first resolution
channel.send(new GenericMessage<>("foo"));
};
}
public static class MySerializer implements Serializer<String> {
#Override
public void configure(Map<String, ?> configs, boolean isKey) {
}
#Override
public byte[] serialize(String topic, String data) {
System.out.println("In my serializer with data of type " + data.getClass().getSimpleName());
return data.getBytes();
}
#Override
public void close() {
}
}
}
and
In my serializer with data of type String

Related

OAuth2Authentication object deserialization (RedisTokenStore)

I'm trying to rewrite some legacy code which used org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore to store the access tokens. I'm currently trying to use the RedisTokenStore instead of the previously used InMemoryTokenStore. The token gets generated and gets stored in Redis fine (Standalone redis configuration), however, deserialization of OAuth2Authentication fails with the following error:
Could not read JSON: Cannot construct instance of `org.springframework.security.oauth2.provider.OAuth2Authentication` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
Since there's no default constructor for this class, the deserialization and mapping to the actual object while looking up from Redis fails.
RedisTokenStore redisTokenStore = new RedisTokenStore(jedisConnectionFactory);
redisTokenStore.setSerializationStrategy(new StandardStringSerializationStrategy() {
#Override
protected <T> T deserializeInternal(byte[] bytes, Class<T> aClass) {
return Utilities.parse(new String(bytes, StandardCharsets.UTF_8),aClass);
}
#Override
protected byte[] serializeInternal(Object o) {
return Objects.requireNonNull(Utilities.convert(o)).getBytes();
}
});
this.tokenStore = redisTokenStore;
public static <T> T parse(String json, Class<T> clazz) {
try {
return OBJECT_MAPPER.readValue(json, clazz);
} catch (IOException e) {
log.error("Jackson2Json failed: " + e.getMessage());
} return null;}
public static String convert(Object data) {
try {
return OBJECT_MAPPER.writeValueAsString(data);
} catch (JsonProcessingException e) {
log.error("Conversion failed: " + e.getMessage());
}
return null;
}
How is OAuth2Authentication object reconstructed when the token is looked up from Redis? Since it does not define a default constructor, any Jackson based serializer and object mapper won't be able to deserialize it.
Again, the serialization works great (since OAuth2Authentication implements Serializable interface) and the token gets stored fine in Redis. It just fails when the /oauth/check_token is called.
What am I missing and how is this problem dealt with while storing access token in Redis?
I solved the issue by writing custom deserializer. It looks like this:
import com.fasterxml.jackson.core.JacksonException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import java.io.IOException;
public class AuthorizationGrantTypeCustomDeserializer extends JsonDeserializer<AuthorizationGrantType> {
#Override
public AuthorizationGrantType deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException {
Root root = p.readValueAs(Root.class);
return root != null ? new AuthorizationGrantType(root.value) : new AuthorizationGrantType("");
}
private static class Root {
public String value;
}
public static SimpleModule generateModule() {
SimpleModule authGrantModule = new SimpleModule();
authGrantModule.addDeserializer(AuthorizationGrantType.class, new AuthorizationGrantTypeCustomDeserializer());
return authGrantModule;
}
}
Then I registered deserializer in objectMapper which is later used by jackson API
ObjectMapper mapper = new ObjectMapper()
.registerModule(AuthorizationGrantTypeCustomDeserializer.generateModule());

RestTemplate - postForObject method - adds entry in database but gives me JsonParseException exception

I am trying to create RestTemplate method for my Movie server application. The PostForObject method works for my entity class (i.e. it writes to the database) but on the browser it gives me huge stack trace of exception. All these concepts are new for me and I don't know why it should fail.
Here is my rest template controller class:-
package com.skill.controller;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.skill.entity.Movie;
#RestController
public class MovieClientController {
#Value("${movie-service.baseurl}")
private String baseURL;
#RequestMapping(path = "/movies/add/",method=RequestMethod.GET)
public void addMovie() {
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.set("Content-Type", "application/json");
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
Movie m1 = new Movie();
m1.setMno(23);
m1.setMname("Fan-4");
m1.setGenre("Fiction");
m1.setRating(1);
HttpEntity<Movie> entity = new HttpEntity<>(m1,headers);
Movie movie = restTemplate.
postForObject(baseURL+"/movies/add/", entity, Movie.class);
System.out.println("The movie id is: " + movie.getMno());
//ResponseEntity<Movie> response = restTemplate.
// exchange(baseURL+"/movies/add/",HttpMethod.POST, entity, Movie.class);
//return new ResponseEntity<Movie>(response.getBody(),HttpStatus.OK);
//return new ResponseEntity<String>("Movie 1 saved ", HttpStatus.ACCEPTED);
}
#RequestMapping(path = "/movies/")
public List<Movie> getAllMovies() {
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<?> entity = new HttpEntity<>(headers);
ResponseEntity<Object[]> responseEntity =
restTemplate.getForEntity(baseURL+"/movies/", Object[].class);
Object[] objects = responseEntity.getBody();
ObjectMapper mapper = new ObjectMapper();
return Arrays.stream(objects)
.map(object -> mapper.convertValue(object, Movie.class))
.collect(Collectors.toList());
}
}
Here is my entity class
package com.skill.entity;
import java.io.Serializable;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Movie implements Serializable {
// #Id
// #Column(name="mno")
#JsonProperty("mno")
private Integer mno;
// #Column(name="mname")
#JsonProperty("mname")
private String mname;
// #Column(name="rating")
#JsonProperty("rating")
private Integer rating;
// #Column(name="genre")
#JsonProperty("genre")
private String genre;
public Movie() {}
public Movie(Integer mno, String mname, Integer rating, String genre) {
super();
this.mno = mno;
this.mname = mname;
this.rating = rating;
this.genre = genre;
}
#JsonProperty("mno")
public Integer getMno() {
return mno;
}
#JsonProperty("mno")
public void setMno(Integer mno) {
this.mno = mno;
}
#JsonProperty("mname")
public String getMname() {
return mname;
}
#JsonProperty("mname")
public void setMname(String mname) {
this.mname = mname;
}
#JsonProperty("rating")
public Integer getRating() {
return rating;
}
#JsonProperty("rating")
public void setRating(Integer rating) {
this.rating = rating;
}
#JsonProperty("genre")
public String getGenre() {
return genre;
}
#JsonProperty("genre")
public void setGenre(String genre) {
this.genre = genre;
}
#Override
public String toString() {
return "Movie [mno=" + mno + ", mname=" + mname + ", rating=" + rating + ", genre=" + genre + "]";
}
}
here is my actual post method in the movie server application. This is for reference:
//add single movie
#PostMapping(path = "/movies/add/",consumes="application/json")
public ResponseEntity<String> addMovie(#RequestBody Movie movie) {
Integer iRating = movieService.addMovie(movie);
if(iRating!=null)
{
return new ResponseEntity<String>("Movie saved with rating: " +iRating, HttpStatus.CREATED);
}
return new ResponseEntity<String>("Movie not saved", HttpStatus.NOT_ACCEPTABLE);
}
here is the stack trace I get when I execute the link in the browser.
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Wed Jun 16 19:38:55 IST 2021
There was an unexpected error (type=Internal Server Error, status=500).
Error while extracting response for type [class com.skill.entity.Movie] and content type [application/json]; nested exception is org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Unrecognized token 'Movie': was expecting (JSON String, Number, Array, Object or token 'null', 'true' or 'false'); nested exception is com.fasterxml.jackson.core.JsonParseException: Unrecognized token 'Movie': was expecting (JSON String, Number, Array, Object or token 'null', 'true' or 'false') at [Source: (PushbackInputStream); line: 1, column: 7]
org.springframework.web.client.RestClientException: Error while extracting response for type [class com.skill.entity.Movie] and content type [application/json]; nested exception is org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Unrecognized token 'Movie': was expecting (JSON String, Number, Array, Object or token 'null', 'true' or 'false'); nested exception is com.fasterxml.jackson.core.JsonParseException: Unrecognized token 'Movie': was expecting (JSON String, Number, Array, Object or token 'null', 'true' or 'false')
at [Source: (PushbackInputStream); line: 1, column: 7]
at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:120)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:778)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:711)
at org.springframework.web.client.RestTemplate.postForObject(RestTemplate.java:437)
at com.skill.controller.MovieClientController.addMovie(MovieClientController.java:51)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:64)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:564)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:197)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:141)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:894)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1063)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:963)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:626)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:733)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:96)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:143)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:374)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:893)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1707)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:630)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.base/java.lang.Thread.run(Thread.java:832)
Caused by: org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Unrecognized token 'Movie': was expecting (JSON String, Number, Array, Object or token 'null', 'true' or 'false'); nested exception is com.fasterxml.jackson.core.JsonParseException: Unrecognized token 'Movie': was expecting (JSON String, Number, Array, Object or token 'null', 'true' or 'false')
at [Source: (PushbackInputStream); line: 1, column: 7]
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:389)
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.read(AbstractJackson2HttpMessageConverter.java:342)
at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:105)
... 58 more
Caused by: com.fasterxml.jackson.core.JsonParseException: Unrecognized token 'Movie': was expecting (JSON String, Number, Array, Object or token 'null', 'true' or 'false')
at [Source: (PushbackInputStream); line: 1, column: 7]
at com.fasterxml.jackson.core.JsonParser._constructError(JsonParser.java:2337)
at com.fasterxml.jackson.core.base.ParserMinimalBase._reportError(ParserMinimalBase.java:720)
at com.fasterxml.jackson.core.json.UTF8StreamJsonParser._reportInvalidToken(UTF8StreamJsonParser.java:3593)
at com.fasterxml.jackson.core.json.UTF8StreamJsonParser._handleUnexpectedValue(UTF8StreamJsonParser.java:2688)
at com.fasterxml.jackson.core.json.UTF8StreamJsonParser._nextTokenNotInObject(UTF8StreamJsonParser.java:870)
at com.fasterxml.jackson.core.json.UTF8StreamJsonParser.nextToken(UTF8StreamJsonParser.java:762)
at com.fasterxml.jackson.databind.ObjectMapper._initForReading(ObjectMapper.java:4684)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4586)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3601)
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:378)
... 60 more
You're adding a Accept header which will expect a JSON parse-able entity - Movie in your case - when you call postForObject.
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON))
But your post method returns a String entity.
Either return a Movie entity from your post method or edit your consumer to expect a String entity.

OpenSessionInViewInterceptor Spring WebSocket

Has anyone implemented OpenSessionInView yet on Spring WebSocket messages yet? I need to implement this and was hoping for some existing material.
I created some test stuff such as below, but I ran into TransactionExceptions, presumably because when you send back a reply, your session needs to cover both sending and receiving messages
public class OpenSessionInViewChannelInterceptor extends ChannelInterceptorAdapter{
#Inject
private SessionFactory sessionFactory;
#Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
SessionFactory sf = sessionFactory;
if (!TransactionSynchronizationManager.hasResource(sf)) {
// New Session to be bound for the current method's scope...
Session session = openSession();
try {
TransactionSynchronizationManager.bindResource(sf, new SessionHolder(session));
return super.preSend(message, channel);
}
finally {
SessionFactoryUtils.closeSession(session);
TransactionSynchronizationManager.unbindResource(sf);
}
}
else {
// Pre-bound Session found -> simply proceed.
return super.preSend(message, channel);
}
}
protected Session openSession() throws DataAccessResourceFailureException {
try {
Session session = sessionFactory.openSession();
session.setFlushMode(FlushMode.MANUAL);
return session;
}
catch (HibernateException ex) {
throw new DataAccessResourceFailureException("Could not open Hibernate Session", ex);
}
}
}
Exception
[MSA] DEBUG [2016-06-21T18:40:05,054] SimpAnnotationMethodMessageHandler.processHandlerMethodException(468) | Searching methods to handle HibernateException
[MSA] ERROR [2016-06-21T18:40:05,057] SimpAnnotationMethodMessageHandler.processHandlerMethodException(478) | Unhandled exception
org.hibernate.HibernateException: Could not obtain transaction-synchronized Session for current thread
at org.springframework.orm.hibernate4.SpringSessionContext.currentSession(SpringSessionContext.java:134) ~[spring-orm-4.1.9.RELEASE.jar:4.1.9.RELEASE]
at org.hibernate.internal.SessionFactoryImpl.getCurrentSession(SessionFactoryImpl.java:1014) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final]
at org.appfuse.dao.hibernate.GenericDaoHibernate.flush(GenericDaoHibernate.java:165) ~[classes/:?]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_60]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_60]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_60]
at java.lang.reflect.Method.invoke(Method.java:497) ~[?:1.8.0_60]
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:317) ~[spring-aop-4.1.9.RELEASE.jar:4.1.9.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190) ~[spring-aop-4.1.9.RELEASE.jar:4.1.9.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157) ~[spring-aop-4.1.9.RELEASE.jar:4.1.9.RELEASE]
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:136) ~[spring-tx-4.1.9.RELEASE.jar:4.1.9.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.1.9.RELEASE.jar:4.1.9.RELEASE]
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:207) ~[spring-aop-4.1.9.RELEASE.jar:4.1.9.RELEASE]

java.io.IOException: INVALID_ARGUMENT: unable to parse key at com.google.cloud.dataflow.sdk.runners.worker.ApplianceShuffleWriter.write

I got the following exception while running some job that read from g3, then group the data by key.
The exception happen during the read.
java.io.IOException: INVALID_ARGUMENT: unable to parse key at com.google.cloud.dataflow.sdk.runners.worker.ApplianceShuffleWriter.write(Native Method) at com.google.cloud.dataflow.sdk.runners.worker.ShuffleSink$ShuffleSinkWriter.outputChunk(ShuffleSink.java:293) at
com.google.cloud.dataflow.sdk.runners.worker.ShuffleSink$ShuffleSinkWriter.close(ShuffleSink.java:288) at
com.google.cloud.dataflow.sdk.util.common.worker.WriteOperation.finish(WriteOperation.java:100) at
com.google.cloud.dataflow.sdk.util.common.worker.MapTaskExecutor.execute(MapTaskExecutor.java:79) at
com.google.cloud.dataflow.sdk.runners.worker.DataflowWorker.executeWork(DataflowWorker.java:288) at
com.google.cloud.dataflow.sdk.runners.worker.DataflowWorker.doWork(DataflowWorker.java:221) at
com.google.cloud.dataflow.sdk.runners.worker.DataflowWorker.getAndPerformWork(DataflowWorker.java:173) at
com.google.cloud.dataflow.sdk.runners.worker.DataflowWorkerHarness$WorkerThread.doWork(DataflowWorkerHarness.java:193) at
com.google.cloud.dataflow.sdk.runners.worker.DataflowWorkerHarness$WorkerThread.call(DataflowWorkerHarness.java:173) at
com.google.cloud.dataflow.sdk.runners.worker.DataflowWorkerHarness$WorkerThread.call(DataflowWorkerHarness.java:160) at
java.util.concurrent.FutureTask.run(FutureTask.java:266) at
java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at
java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at
java.lang.Thread.run(Thread.java:745)
Any ideas?
This exception throws when you try to apply GroupByKey but some of the mapped keys are null.
This code throws exception:
pCollection
.apply(ParDo.of(new DoFn<KV<MyObject, MyObject>, Object>() {
#Override
public void processElement(ProcessContext c) throws Exception {
c.output(KV.of(null, c.element()));
}
}))
.apply(GroupByKey.<String, Statusable>create())
You can't write null key.
Therefore when your key is nullable you must do something like this:
pCollection
.apply(ParDo.of(new DoFn<KV<MyObject, MyObject>, Object>() {
#Override
public void processElement(ProcessContext c) throws Exception {
String key == c.element().getKeyField();
if (key == null){
// Handle some how....
key = ... // not null value
}
c.output(KV.of(key, c.element()));
}
}))

SDN:4 Value injection into Converter fails

I wrote a custom converter for my graph property as shown below.
Entity class
#NodeEntity(label = "Person")
public class Person extends AbstractEntity {
#Property(name = "accessCount")
private Long accessCount;
#Property(name = "lastAccessDate")
#Convert(LocalDateTimeConverter.class)
private LocalDateTime lastAccessDate;
public Long getAccessCount() {
return accessCount;
}
public void setAccessCount(final Long accessCount) {
this.accessCount = accessCount;
}
public LocalDateTime getLastAccessDate() {
return lastAccessDate;
}
public void setLastAccessDate(final LocalDateTime lastAccessDate) {
this.lastAccessDate = lastAccessDate;
}
}
Converter
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import org.apache.commons.lang3.StringUtils;
import org.neo4j.ogm.typeconversion.AttributeConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
#Component
public class LocalDateTimeConverter implements AttributeConverter<LocalDateTime, String> {
private static final Logger LOG = LoggerFactory.getLogger(LocalDateTimeConverter.class);
#Value("${neo4j.dateTime.format:yyyy-MM-dd HH:mm:ss.SSS}")
private String dateTimeFormat;
#Override
public String toGraphProperty(final LocalDateTime value) {
LOG.debug("Converting local date time: {} to string ...", value);
if (value == null) {
return "";
}
return String.valueOf(value.format(getDateTimeFormatter()));
}
#Override
public LocalDateTime toEntityAttribute(final String value) {
LOG.debug("Converting string: {} to local date time ...", value);
if (StringUtils.isBlank(value)) {
return null;
}
return LocalDateTime.parse(value, getDateTimeFormatter());
}
public DateTimeFormatter getDateTimeFormatter() {
return DateTimeFormatter.ofPattern(dateTimeFormat);
}
}
It's unit test passes
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = TestContextConfiguration.class)
#DirtiesContext
#TestExecutionListeners(inheritListeners = false, listeners = { DataSourceDependencyInjectionTestExecutionListener.class })
public class LocalDateTimeConverterTest {
public static final String DATE_TIME_VALUE = "2015-06-22 13:05:04.546";
#Autowired
protected LocalDateTimeConverter localDateTimeConverter;
#Test
public void should_get_date_time_formatter() {
final DateTimeFormatter dateTimeFormatter = localDateTimeConverter.getDateTimeFormatter();
assertNotNull(dateTimeFormatter);
}
#Test
public void should_convert_local_date_time_property_from_graph_property_string_for_database() throws Exception {
final LocalDateTime localDateTime = LocalDateTime.of(2015, Month.JUNE, 22, 13, 5, 4, 546000000);
final String actual = localDateTimeConverter.toGraphProperty(localDateTime);
final String expected = localDateTime.format(localDateTimeConverter.getDateTimeFormatter());
assertEquals(expected, actual);
}
#Test
public void should_convert_string_from_database_to_local_date_time() throws Exception {
final LocalDateTime localDateTime = localDateTimeConverter.toEntityAttribute(DATE_TIME_VALUE);
assertNotNull(localDateTime);
assertThat(localDateTime.getDayOfMonth(), equalTo(22));
assertThat(localDateTime.getMonthValue(), equalTo(6));
assertThat(localDateTime.getYear(), equalTo(2015));
assertThat(localDateTime.getHour(), equalTo(13));
assertThat(localDateTime.getMinute(), equalTo(5));
assertThat(localDateTime.getSecond(), equalTo(4));
assertThat(localDateTime.getNano(), equalTo(546000000));
}
}
However, when I'm trying to use it from a repository as shown below.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = TestContextConfiguration.class)
#DirtiesContext
#TestExecutionListeners(inheritListeners = false, listeners = { DataSourceDependencyInjectionTestExecutionListener.class })
public class PersonRepositoryTest extends AbstractRepositoryTest<CypherFilesPopulator> {
private static final Logger LOG = LoggerFactory.getLogger(PersonRepositoryTest.class);
public static final String CQL_DATASET_FILE = "src/test/resources/dataset/person-repository-dataset.cql";
#Autowired
PersonRepository personRepository;
#Test
public void should_find_all_persons() {
LOG.debug("Test: Finding all persons ...");
final Iterable<Person> persons = personRepository.findAll();
persons.forEach(person -> {LOG.debug("Person: {}", person);});
}
#Override
public CypherFilesPopulator assignDatabasePopulator() {
return DatabasePopulatorUtil.createCypherFilesPopulator(Collections.singletonList(CQL_DATASET_FILE));
}
}
My unit test fails as value injection isn't happening.
org.neo4j.ogm.metadata.MappingException: Error mapping GraphModel to instance of com.example.model.node.Person
at org.neo4j.ogm.mapper.GraphEntityMapper.mapEntities(GraphEntityMapper.java:97)
at org.neo4j.ogm.mapper.GraphEntityMapper.map(GraphEntityMapper.java:69)
at org.neo4j.ogm.session.response.SessionResponseHandler.loadAll(SessionResponseHandler.java:181)
at org.neo4j.ogm.session.delegates.LoadByTypeDelegate.loadAll(LoadByTypeDelegate.java:69)
at org.neo4j.ogm.session.delegates.LoadByTypeDelegate.loadAll(LoadByTypeDelegate.java:99)
at org.neo4j.ogm.session.Neo4jSession.loadAll(Neo4jSession.java:119)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:317)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
at org.springframework.aop.support.DelegatingIntroductionInterceptor.doProceed(DelegatingIntroductionInterceptor.java:133)
at org.springframework.aop.support.DelegatingIntroductionInterceptor.invoke(DelegatingIntroductionInterceptor.java:121)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:207)
at com.sun.proxy.$Proxy110.loadAll(Unknown Source)
at org.springframework.data.neo4j.repository.GraphRepositoryImpl.findAll(GraphRepositoryImpl.java:123)
at org.springframework.data.neo4j.repository.GraphRepositoryImpl.findAll(GraphRepositoryImpl.java:118)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.executeMethodOn(RepositoryFactorySupport.java:475)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:460)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:432)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:61)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:281)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:136)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:207)
at com.sun.proxy.$Proxy124.findAll(Unknown Source)
at com.example.repository.PersonRepositoryTest.should_find_all_persons(PersonRepositoryTest.java:36)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:73)
at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:82)
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:73)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:224)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:83)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:68)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:163)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:78)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:212)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:68)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140)
Caused by: java.lang.NullPointerException: pattern
at java.util.Objects.requireNonNull(Objects.java:228)
at java.time.format.DateTimeFormatterBuilder.appendPattern(DateTimeFormatterBuilder.java:1571)
at java.time.format.DateTimeFormatter.ofPattern(DateTimeFormatter.java:534)
at com.example.converter.LocalDateTimeConverter.getDateTimeFormatter(LocalDateTimeConverter.java:41)
at com.example.converter.LocalDateTimeConverter.toEntityAttribute(LocalDateTimeConverter.java:37)
at com.example.converter.LocalDateTimeConverter.toEntityAttribute(LocalDateTimeConverter.java:14)
at org.neo4j.ogm.entityaccess.FieldWriter.write(FieldWriter.java:64)
at org.neo4j.ogm.mapper.GraphEntityMapper.writeProperty(GraphEntityMapper.java:164)
at org.neo4j.ogm.mapper.GraphEntityMapper.setProperties(GraphEntityMapper.java:129)
at org.neo4j.ogm.mapper.GraphEntityMapper.mapNodes(GraphEntityMapper.java:110)
at org.neo4j.ogm.mapper.GraphEntityMapper.mapEntities(GraphEntityMapper.java:94)
... 74 more
I'm wondering how my converter object is instantiated by SDN4? I can't spot what I'm doing wrong here. Similar approach used to work in SDN 3.4. It started to break when I upgraded to SDN 4.
This is happening because it's not actually Spring that constructs AttributeConverter instances in this case. AttributeConverter comes from the underlying object-graph mapper (OGM) and this, by design, is not Spring-aware and therefore disregards any Spring annotations on classes that it manages.
However, if you change the #Convert annotation on the Person field by specifying the target type instead of the AttributeConverter class then you can use Spring's ConversionService instead. You can register the Spring converter that you want with a MetaDataDrivenConversionService and the framework should use this for the conversion.
Your meta-data-driven conversion service can be constructed in your Neo4jConfiguration subclass like this:
#Bean
public ConversionService springConversionService() {
return new MetaDataDrivenConversionService(getSessionFactory().metaData());
}

Resources