Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Entity instantiation fails [DATACMNS-1416] #1850

Closed
spring-projects-issues opened this issue Nov 2, 2018 · 13 comments
Closed

Entity instantiation fails [DATACMNS-1416] #1850

spring-projects-issues opened this issue Nov 2, 2018 · 13 comments
Assignees
Labels
in: mapping status: invalid

Comments

@spring-projects-issues
Copy link

spring-projects-issues commented Nov 2, 2018

Abhijit Sarkar opened DATACMNS-1416 and commented

Fails to instantiate an entity as show below. Works with 2.0.11.RELEASE.

Caused by: org.springframework.data.mapping.model.MappingInstantiationException: Failed to instantiate com.mycompany.Version using constructor fun <init>(kotlin.String?, kotlin.String, com.mycompany.Artifact, com.mycompany.Version?, kotlin.collections.Set<com.mycompany.Dependency>?): com.mycompany.Version with arguments com.mycompany:whatever:1.0.0-SNAPSHOT,1.0.0-SNAPSHOT,null,null,null,24,null
	at org.springframework.data.convert.KotlinClassGeneratingEntityInstantiator$DefaultingKotlinClassInstantiatorAdapter.createInstance(KotlinClassGeneratingEntityInstantiator.java:209)
	at org.springframework.data.convert.ClassGeneratingEntityInstantiator.createInstance(ClassGeneratingEntityInstantiator.java:84)
	at org.springframework.data.neo4j.conversion.Neo4jOgmEntityInstantiatorAdapter.createInstance(Neo4jOgmEntityInstantiatorAdapter.java:58)
	at org.neo4j.ogm.metadata.reflect.EntityFactory.instantiate(EntityFactory.java:121)
	at org.neo4j.ogm.metadata.reflect.EntityFactory.newObject(EntityFactory.java:90)
	at org.neo4j.ogm.context.GraphEntityMapper.mapNodes(GraphEntityMapper.java:232)
	at org.neo4j.ogm.context.GraphEntityMapper.mapEntities(GraphEntityMapper.java:207)
	... 74 more
Caused by: java.lang.IllegalArgumentException: Parameter specified as non-null is null:

Affects: 2.1.2 (Lovelace SR2)

Attachments:

Issue Links:

@spring-projects-issues
Copy link
Author

spring-projects-issues commented Nov 2, 2018

Oliver Drotbohm commented

Looks like your third constructor parameter is defined as non-nullable, but in the source values that value is null

@spring-projects-issues
Copy link
Author

spring-projects-issues commented Nov 2, 2018

Abhijit Sarkar commented

It looks that way, but the 3rd param is set in the test that fails, and how do you explain the same code working with 2.0.11.RELEASE (same Kotlin version)?

@spring-projects-issues
Copy link
Author

spring-projects-issues commented Nov 2, 2018

Oliver Drotbohm commented

There have been changes made to the Neo4j module to conform to our immutable type instantiation alogorithm. Apparently, that has changed and doesn't forward the parameter as it did before. Michael Simons – does that ring a bell?

@spring-projects-issues
Copy link
Author

spring-projects-issues commented Nov 3, 2018

Michael Simons commented

Thanks for pointing me to this.

Abhijt, would you mind sharing your Test as a reproducer. We fixed bugs in the specific area, but the ongoing commit that will allow full immutable types is still open and not merged.

@spring-projects-issues
Copy link
Author

spring-projects-issues commented Nov 3, 2018

Michael Simons commented

I can confirm a bug (SDN 5.1.2, Spring Boot 2.1), but what I found irritating that you mention that it should work with SD Commons 2.0.11.

I tried the same with SDN 5.0.11, Spring Boot 2.0.6 and I end up with a

Caused by: java.lang.NoSuchMethodException: com.example.pctor.KotlinPerson.<init>()
	at java.lang.Class.getConstructor0(Class.java:3082) ~[na:1.8.0_181]
	at java.lang.Class.getDeclaredConstructor(Class.java:2178) ~[na:1.8.0_181]
	at org.neo4j.ogm.metadata.reflect.ReflectionEntityInstantiator.createInstance(ReflectionEntityInstantiator.java:37) ~[neo4j-ogm-core-3.1.4.jar:3.1.4]
	... 56 common frames omitted

@spring-projects-issues
Copy link
Author

spring-projects-issues commented Nov 3, 2018

Abhijit Sarkar commented

Entity classes:

@NodeEntity
data class Group(@Id var name: String)

@NodeEntity
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator::class, property = "id")
data class Artifact(
    @Id @GeneratedValue(strategy = ArtifactIdStrategy::class) var id: String? = null,
    var name: String,
    @Relationship(type = "BELONGS_TO") var belongsTo: Group
) {
    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (other !is Artifact) return false

        if (name != other.name) return false
        if (belongsTo != other.belongsTo) return false

        return true
    }

    override fun hashCode(): Int {
        return Objects.hash(name, belongsTo)
    }
}

@NodeEntity
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator::class, property = "id")
data class Version(
    @Id @GeneratedValue(strategy = VersionIdStrategy::class) var gav: String? = null,
    var name: String,
    @Relationship(type = "VERSION_OF") var versionOf: Artifact,
    @Relationship(type = "CHILD_OF") var parent: Version? = null,
    @Relationship(type = "DEPENDS_ON") var dependencies: Set<Dependency>? = setOf()
) {
    fun addDependency(dependency: Version): Version {
        dependencies = (dependencies ?: setOf()) + Dependency(
            dependent = this,
            other = dependency
        )
        return this
    }

    // override equals and hashCode to avoid Version.hashCode calling Dependency.hashCode calling Version.hashCode
    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (other !is Version) return false

        if (gav != other.gav) return false

        return true
    }

    override fun hashCode() = Objects.hashCode(gav)
    override fun toString() =
        "Version(gav=$gav, parent=${parent?.gav}, dependencies=[${dependencies?.map { it.other.gav }?.joinToString()}])"
}

@RelationshipEntity(type = "DEPENDS_ON")
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator::class, property = "id")
data class Dependency(
    @Id @GeneratedValue var id: Long? = null,
    @StartNode var dependent: Version,
    @EndNode var other: Version
) {
    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (other !is Dependency) return false

        if (dependent != other.dependent) return false
        if (this.other != other.other) return false

        return true
    }

    override fun hashCode(): Int {
        return Objects.hash(dependent, other)
    }
}

Tests:

@RunWith(SpringRunner::class)
@SpringBootTest(
    classes = [VersionRepositoryTestApplication::class],
    webEnvironment = SpringBootTest.WebEnvironment.NONE
)
@ActiveProfiles("test")
class VersionRepositoryTest {
    companion object {
        private const val GROUP = "com.mycompany.abc"
        private const val VERSION = "1.0.0-SNAPSHOT"
    }

    @Autowired
    private lateinit var versionRepository: VersionRepository
    private lateinit var abcCommons: Version
    private lateinit var subscription: Version
    private lateinit var auth: Version
    private lateinit var gateway: Version
    private lateinit var localSearch: Version

    @BeforeTest
    fun beforeEach() {
        abcCommons = Version(
            name = VERSION,
            versionOf = Artifact(
                name = "abc-commons",
                belongsTo = Group(GROUP)
            )
        )
            .apply {
                versionRepository.save(this)
            }
        subscription = Version(
            name = VERSION,
            versionOf = Artifact(
                name = "subscription",
                belongsTo = Group(GROUP)
            )
        )
            .apply {
                addDependency(abcCommons)
                versionRepository.save(this)
            }

        auth = Version(
            name = VERSION,
            versionOf = Artifact(
                name = "auth",
                belongsTo = Group(GROUP)
            )
        )
            .apply {
                addDependency(subscription)
                versionRepository.save(this)
            }
        gateway = Version(
            name = VERSION,
            versionOf = Artifact(
                name = "gateway",
                belongsTo = Group(GROUP)
            )
        )
            .apply {
                addDependency(auth)
                addDependency(abcCommons)
                versionRepository.save(this)
            }

        localSearch = Version(
            name = VERSION,
            versionOf = Artifact(
                name = "local-search",
                belongsTo = Group(GROUP)
            )
        )
            .apply {
                addDependency(abcCommons)
                addDependency(subscription)
                versionRepository.save(this)
            }
    }

    @AfterTest
    fun afterEach() {
        versionRepository.deleteAll()
    }

    @Test
    fun `should find version by gav`() {
        val version = versionRepository.findById(abcCommons.gav!!)
        assertThat(version.isPresent).isTrue()
        assertThat(version.get().gav).isEqualTo(abcCommons.gav!!)
    }

    @Test
    fun `should persist dependencies`() {
        val v1 = versionRepository.findById(subscription.gav!!)
        assertThat(v1.isPresent).isTrue()
        assertThat(v1.get().dependencies).hasSize(1)

        val v2 = versionRepository.findById(localSearch.gav!!)
        assertThat(v2.isPresent).isTrue()
        assertThat(v2.get().dependencies).hasSize(2)
    }

    @Test
    fun `should not load any relationships`() {
        val maybe = versionRepository.findById(localSearch.gav!!, 0)
        assertThat(maybe.isPresent).isTrue()
        val version = maybe.get()
        assertThat(version.versionOf).isNull()
    }

    @Test
    fun `should load artifact but not group`() {
        val maybe = versionRepository.findById(localSearch.gav!!, 1)
        assertThat(maybe.isPresent).isTrue()
        val version = maybe.get()
        assertThat(version.versionOf).isNotNull()
        assertThat(version.versionOf.belongsTo).isNull()
    }

    @Test
    fun `should load all relationships`() {
        val maybe = versionRepository.findById(localSearch.gav!!, 2)
        assertThat(maybe.isPresent).isTrue()
        val version = maybe.get()
        assertThat(version.versionOf).isNotNull()
        assertThat(version.versionOf.belongsTo).isNotNull()
    }

    @Test
    fun `should not add same dependency more than once`() {
        var maybe = versionRepository.findById(localSearch.gav!!, 2)
        assertThat(maybe.isPresent).isTrue()
        var version = maybe.get()
        assertThat(version.dependencies!!.find { it.other.gav == abcCommons.gav!! }).isNotNull()
        val initial = version.dependencies!!.size

        for (i in 1..5) {
            version.addDependency(abcCommons)
        }
        assertThat(version.dependencies!!.size).isEqualTo(initial)

        versionRepository.save(version)

        maybe = versionRepository.findById(localSearch.gav!!)
        assertThat(maybe.isPresent).isTrue()
        version = maybe.get()
        assertThat(version.dependencies!!.size).isEqualTo(initial)
    }

    @Test
    fun `should rank gavs`() {
        val (guaranteed, rest) = versionRepository.findAllOrderByRankDesc().chunked(3)

        assertThat(guaranteed).containsExactly(abcCommons.gav, subscription.gav, auth.gav)
        assertThat(rest).containsExactlyInAnyOrder(gateway.gav, localSearch.gav)
    }
}

@SpringBootApplication
class VersionRepositoryTestApplication

@spring-projects-issues
Copy link
Author

spring-projects-issues commented Nov 3, 2018

Oliver Drotbohm commented

Would you mind adding that code in executable form like a zipped project or Git repository? We cans just copy and paste code from the ticket somewhere

@spring-projects-issues
Copy link
Author

spring-projects-issues commented Nov 3, 2018

Abhijit Sarkar commented

I'm not sure what you mean by "We cans just copy and paste code", because I was able to copy-paste into text files, and create an archive with those. See attached DATACMNS-1416.zip. HTH.

BTW, the ticket is still in waiting for feedback status. Do you need anything more from me in order to substantiate this as a regression bug?

@spring-projects-issues
Copy link
Author

spring-projects-issues commented Nov 4, 2018

Oliver Drotbohm commented

What I meant is: to plain source files don't reproduce the error. We need something executable, i.e. a simple maven project to run this. Otherwise there's no way for us to verify dependency versions that might affect the problem etc

@spring-projects-issues
Copy link
Author

spring-projects-issues commented Nov 7, 2018

Michael Simons commented

Here is a simple reproducer I used for the related ticket DATAGRAPH-1148:

[^DATAGRAPH-1148.zip]

Oliver Drotbohm Please decide where to track this. I'd say this is a Neo4j issue

@spring-projects-issues
Copy link
Author

spring-projects-issues commented Nov 7, 2018

Abhijit Sarkar commented

Michael Simons thank you for not giving up. I got busy and wasn't also feeling very enthusiastic about stripping down a large industrial project to the basics, not knowing what to keep and what not to

@spring-projects-issues
Copy link
Author

spring-projects-issues commented Mar 12, 2019

Abhijit Sarkar commented

Michael Simons This ticket is stuck the wrong status

@spring-projects-issues
Copy link
Author

spring-projects-issues commented Nov 13, 2019

Mark Paluch commented

Closing as this is a Spring Data Neo4j issue

@spring-projects-issues spring-projects-issues added status: invalid in: mapping labels Dec 30, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: mapping status: invalid
Projects
None yet
Development

No branches or pull requests

2 participants