spring-projects / spring-data-neo4j

Provide support to increase developer productivity in Java when using Neo4j. Uses familiar Spring concepts such as a template classes for core API usage and lightweight repository style data access.
http://spring.io/projects/spring-data-neo4j
Apache License 2.0
825 stars 619 forks source link

Fix possible ArrayIndexOutOfBoundsException in MetaDataDrivenConversionService during startup. [DATAGRAPH-1131] #1695

Closed spring-projects-issues closed 5 years ago

spring-projects-issues commented 5 years ago

Torsten Kuhnhenne opened DATAGRAPH-1131 and commented

When i use an abstract base class for an AttributeConverter with specific implementations i got an ArrayIndexOutOfBoundsException in MetaDataDrivenConversionService at line #77 when the converter is registered.

The same code works with SDN 5.0.10. I didn't find any differences in MetaDataDrivenConversionService between 5.0.10 and 5.1.0, so i have no idea why it doesn't work in 5.1.0.  

Base class for the converter:

public abstract class AbstractObjectToJSONStringConverter<T> implements AttributeConverter<T,String> {

    private final ObjectMapper mapper = new ObjectMapper();

    @Override
    public String toGraphProperty( final T value ) {
        if ( value == null ) {
            return null;
        }
        String json = null;
        try {
            json = this.mapper.writeValueAsString( value );
        }
        catch ( final IOException e ) {
            final Logger logger = LoggerFactory.getLogger( getClass() );
            logger.warn( e.getMessage(), e );
        }
        return json;
    }

    @Override
    public T toEntityAttribute( final String value ) {
        if ( value == null ) {
            return null;
        }
        T deserialised = null;
        try {
            deserialised = this.mapper.readValue( value, getTypeClass() );
        }
        catch ( final IOException e ) {
            final Logger logger = LoggerFactory.getLogger( getClass() );
            logger.warn( e.getMessage(), e );
        }
        return deserialised;
    }

    protected abstract Class<T> getTypeClass();
}

The implementation class:

public class Neo4jOAuth2RefreshTokenToStringConverter extends AbstractObjectToJSONStringConverter<OAuth2RefreshToken> {

    @Override
    protected Class<OAuth2RefreshToken> getTypeClass() {
        return OAuth2RefreshToken.class;
    }
}

When i add an additional implements AttributeConverter<OAuth2RefreshToken,String> to the implementation class everythings works as excepected.

Stacktrace:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'neo4jOgmEntityInstantiatorConfigurationBean': Bean instantiation via constructor failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.data.neo4j.repository.config.Neo4jOgmEntityInstantiatorConfigurationBean]: Constructor threw exception; nested exception is java.lang.ArrayIndexOutOfBoundsException: 0
        at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:303)
        at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:284)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1307)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1153)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:538)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:498)
        at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320)
        at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
        at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318)
        at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:848)
        at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:865)
        at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:548)
        at org.springframework.web.context.ContextLoader.configureAndRefreshWebApplicationContext(ContextLoader.java:400)
        at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:291)
        at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:103)
        at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:4641)
        at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5109)
        at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183)
        at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1427)
        at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1417)
        at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
        at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75)
        at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:140)
        at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:943)
        at org.apache.catalina.core.StandardHost.startInternal(StandardHost.java:839)
        at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183)
        at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1427)
        at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1417)
        at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
        at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75)
        at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:140)
        at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:943)
        at org.apache.catalina.core.StandardEngine.startInternal(StandardEngine.java:258)
        at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183)
        at org.apache.catalina.core.StandardService.startInternal(StandardService.java:422)
        at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183)
        at org.apache.catalina.core.StandardServer.startInternal(StandardServer.java:770)
        at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183)
        at org.apache.catalina.startup.Catalina.start(Catalina.java:682)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base/java.lang.reflect.Method.invoke(Method.java:564)
        at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:350)
        at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:492)
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.data.neo4j.repository.config.Neo4jOgmEntityInstantiatorConfigurationBean]: Constructor threw exception; nested exception is java.lang.ArrayIndexOutOfBoundsException: 0
        at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:184)
        at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:117)
        at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:299)
        ... 45 common frames omitted
Caused by: java.lang.ArrayIndexOutOfBoundsException: 0
        at org.springframework.data.neo4j.conversion.MetaDataDrivenConversionService.addWrappedConverter(MetaDataDrivenConversionService.java:77)
        at org.springframework.data.neo4j.conversion.MetaDataDrivenConversionService.<init>(MetaDataDrivenConversionService.java:52)
        at org.springframework.data.neo4j.repository.config.Neo4jOgmEntityInstantiatorConfigurationBean.lambda$new$0(Neo4jOgmEntityInstantiatorConfigurationBean.java:39)
        at org.springframework.beans.factory.ObjectProvider.getIfUnique(ObjectProvider.java:122)
        at org.springframework.data.neo4j.repository.config.Neo4jOgmEntityInstantiatorConfigurationBean.<init>(Neo4jOgmEntityInstantiatorConfigurationBean.java:39)
        at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
        at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)

Affects: 5.1 GA (Lovelace)

Backported to: 5.1.1 (Lovelace SR1), 5.0.11 (Kay SR11)

spring-projects-issues commented 5 years ago

Michael Simons commented

How to you register your custom converter?

spring-projects-issues commented 5 years ago

Michael Simons commented

Also: Are you running this in Spring Boot or plain Spring? If the last one is the case, could you maybe provide an example using the templates? https://github.com/neo4j-examples/neo4j-sdn-ogm-issue-report-template

spring-projects-issues commented 5 years ago

Torsten Kuhnhenne commented

Via an annotation in a domain class:

@Property
@Convert(Neo4jOAuth2RefreshTokenToStringConverter.class)
private OAuth2RefreshToken oAuth2RefreshToken;

The class is annotated with @NodeEntity

spring-projects-issues commented 5 years ago

Torsten Kuhnhenne commented

It`s a plain Spring project

spring-projects-issues commented 5 years ago

Michael Simons commented

Thanks, that helps. Can now reproduce the behavior

spring-projects-issues commented 5 years ago

Michael Simons commented

Thanks again for bringing this to our attention.

Your code broke because we fixed an issue with the MetaDataDrivenConversionService. It hasn't been correctly initialized before (actually, in your case probably not at all, however, converters should have been applied nevertheless. I'd be happy if you can check this). That error has been fixed.

Which led now to another error in MetaDataDrivenConversionService which we are fixing in the next service release of Lovelace

spring-projects-issues commented 5 years ago

Michael Simons commented

This is fixed and also back ported to 5.0.11. If you want to try out your project with the fixed version of Lovelace until it's release, Torsten Kuhnhenne please use this version

<dependency>     <groupId>org.springframework.data</groupId>     <artifactId>spring-data-neo4j</artifactId>     <version>5.1.1.BUILD-SNAPSHOT</version> </dependency>

Edit: Sorry, I give up on the formatting of this answer. Snapshot repositories is under https://repo.spring.io/snapshot

spring-projects-issues commented 5 years ago

Torsten Kuhnhenne commented

Michael Simons: Thanks a lot for the quick reply and fix! The snapshot version works for me :)

 

In response to your comment:

In 5.0.10 the converters are applied. Or should I check if the MetaDataDrivenConversionService is initialized or not? 

spring-projects-issues commented 5 years ago

Michael Simons commented

Thanks for your feedback!

In 5.0.10 the converters are applied. Or should I check if the MetaDataDrivenConversionService is initialized or not?

The later. I'd guess your converters are applied through other means in SDN

spring-projects-issues commented 5 years ago

Torsten Kuhnhenne commented

You are right, the constructor of the class MetaDataDrivenConversionService is never called when i start my application with SDN 5.0.10.

Maybe the converters are applied through the OGM?

spring-projects-issues commented 5 years ago

Michael Simons commented

Thanks for baring with me.

The MetaDataDrivenConversionService has been laying around basically unused in SDN 5.0.x. There are tests that verifies its integration with existing Spring Converters.

Converters on properties have been applied directly.

As you might have read in the release notes, we support non-default constructors with SDN 5.1. To make this as smooth as possible, we also want to have converters in place and use existing Spring converters if possible. If there are non at all or not a unique one, we now make use of our own.

In your SDN 5.1.x without Boot, there is no preexisting Conversion Service, so we eagerly instantiated the MetaDataDrivenConversionService which lead to the discovery of your error.

In short: Your application worked as expected.