2

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.

Viswanath
  • 1,413
  • 13
  • 25

1 Answers1

3

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());
}
ATG
  • 1,679
  • 14
  • 25
  • Thanks! I just wanted to confirm that my **AttributeConverter** wasn't injected by spring but created in a good old fashion way by OGM in the cotext of SDN. I am familiar with Spring's conversion service. I used that approach prior to upgrading my implementation to SDN4. I just wanted to try using an **AttributeConverter** because with custom implementation of conversion service, one would've to write converters in both directions and register them both. I have an inclination to have both of them in the same class. :) – Viswanath Sep 15 '15 at 12:04