From 074a815bc13118d3c29fb706ae0131f736a0041a Mon Sep 17 00:00:00 2001 From: Michael Simons Date: Mon, 24 Aug 2020 09:01:55 +0200 Subject: [PATCH] GH-823 - Add an example for Kotlins inline classes. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Kotlin provides so called „Inline classes“ (https://kotlinlang.org/docs/reference/inline-classes.html), wrapping standard types into a dedicated type. This makes them a nice fit for IDs etc. The object mapping process works ootb, the parameter mapping needs some extra work: One has to provide custom serializers for all data classes being used (see `IdTypes`). Those serializers can be registered with a dedicated `SimpleModule`. This module needs to be added to the `ObjectMapper` instance used by OGM to convert parameters: ``` ObjectMapperFactory.objectMapper() .registerModule(KotlinModule()) .registerModule(IdTypesModule()) ``` The kotlin jackson module may detect them at some point in the future itself (See https://github.com/FasterXML/jackson-module-kotlin/issues/199), but until than that work is necessary. This closes #822. --- .../neo4j-ogm-integration-tests/pom.xml | 5 +++ .../org/neo4j/ogm/domain/gh822/IdTypes.kt | 44 +++++++++++++++++++ .../kotlin/org/neo4j/ogm/domain/gh822/User.kt | 28 ++++++++++++ .../org/neo4j/ogm/kotlin/KotlinInteropTest.kt | 26 ++++++++++- .../neo4j/ogm/kotlin/KotlinMetaDataTest.kt | 9 ++++ 5 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 neo4j-ogm-tests/neo4j-ogm-integration-tests/src/test/kotlin/org/neo4j/ogm/domain/gh822/IdTypes.kt create mode 100644 neo4j-ogm-tests/neo4j-ogm-integration-tests/src/test/kotlin/org/neo4j/ogm/domain/gh822/User.kt diff --git a/neo4j-ogm-tests/neo4j-ogm-integration-tests/pom.xml b/neo4j-ogm-tests/neo4j-ogm-integration-tests/pom.xml index fb88e82184..3142c8fae8 100644 --- a/neo4j-ogm-tests/neo4j-ogm-integration-tests/pom.xml +++ b/neo4j-ogm-tests/neo4j-ogm-integration-tests/pom.xml @@ -142,6 +142,11 @@ org.jetbrains.kotlin kotlin-stdlib-jdk8 + + com.fasterxml.jackson.module + jackson-module-kotlin + + org.jetbrains.kotlin kotlin-test diff --git a/neo4j-ogm-tests/neo4j-ogm-integration-tests/src/test/kotlin/org/neo4j/ogm/domain/gh822/IdTypes.kt b/neo4j-ogm-tests/neo4j-ogm-integration-tests/src/test/kotlin/org/neo4j/ogm/domain/gh822/IdTypes.kt new file mode 100644 index 0000000000..2eeabf7056 --- /dev/null +++ b/neo4j-ogm-tests/neo4j-ogm-integration-tests/src/test/kotlin/org/neo4j/ogm/domain/gh822/IdTypes.kt @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2002-2020 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.ogm.domain.gh822 + +import com.fasterxml.jackson.core.JsonGenerator +import com.fasterxml.jackson.databind.SerializerProvider +import com.fasterxml.jackson.databind.ser.std.StdSerializer +import java.io.IOException +import java.io.Serializable + +inline class StringID(val value: String) : Serializable + +private class StringIDSerializer : StdSerializer(StringID::class.java) { + @Throws(IOException::class) + override fun serialize(s: StringID, jsonGenerator: JsonGenerator, + serializerProvider: SerializerProvider) { + jsonGenerator.writeObject(s.value) + } +} + +class IdTypesModule : com.fasterxml.jackson.databind.module.SimpleModule() { + + init { + addSerializer(StringIDSerializer()) + } +} + + diff --git a/neo4j-ogm-tests/neo4j-ogm-integration-tests/src/test/kotlin/org/neo4j/ogm/domain/gh822/User.kt b/neo4j-ogm-tests/neo4j-ogm-integration-tests/src/test/kotlin/org/neo4j/ogm/domain/gh822/User.kt new file mode 100644 index 0000000000..19b4c5fd9b --- /dev/null +++ b/neo4j-ogm-tests/neo4j-ogm-integration-tests/src/test/kotlin/org/neo4j/ogm/domain/gh822/User.kt @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2002-2020 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.ogm.domain.gh822 + +import org.neo4j.ogm.annotation.Id +import org.neo4j.ogm.annotation.NodeEntity + +@NodeEntity +data class User( + @Id var userId: StringID? = null, + val name: String +) diff --git a/neo4j-ogm-tests/neo4j-ogm-integration-tests/src/test/kotlin/org/neo4j/ogm/kotlin/KotlinInteropTest.kt b/neo4j-ogm-tests/neo4j-ogm-integration-tests/src/test/kotlin/org/neo4j/ogm/kotlin/KotlinInteropTest.kt index aee5eb5815..409fa196fb 100644 --- a/neo4j-ogm-tests/neo4j-ogm-integration-tests/src/test/kotlin/org/neo4j/ogm/kotlin/KotlinInteropTest.kt +++ b/neo4j-ogm-tests/neo4j-ogm-integration-tests/src/test/kotlin/org/neo4j/ogm/kotlin/KotlinInteropTest.kt @@ -18,6 +18,7 @@ */ package org.neo4j.ogm.kotlin +import com.fasterxml.jackson.module.kotlin.KotlinModule import org.assertj.core.api.Assertions.assertThat import org.junit.* import org.neo4j.driver.AuthTokens @@ -27,6 +28,7 @@ import org.neo4j.driver.Values import org.neo4j.harness.ServerControls import org.neo4j.harness.TestServerBuilders import org.neo4j.ogm.config.Configuration +import org.neo4j.ogm.config.ObjectMapperFactory import org.neo4j.ogm.cypher.ComparisonOperator import org.neo4j.ogm.cypher.Filter import org.neo4j.ogm.cypher.Filters @@ -38,6 +40,7 @@ import org.neo4j.ogm.domain.delegation.KotlinAImpl import org.neo4j.ogm.domain.gh696.Lion import org.neo4j.ogm.domain.gh696.Zebra import org.neo4j.ogm.domain.gh696.ZooKotlin +import org.neo4j.ogm.domain.gh822.* import org.neo4j.ogm.session.* import kotlin.test.assertNotNull @@ -67,8 +70,13 @@ class KotlinInteropTest { sessionFactory = SessionFactory(ogmConfiguration, MyNode::class.java.`package`.name, KotlinAImpl::class.java.`package`.name, - ZooKotlin::class.java.`package`.name + ZooKotlin::class.java.`package`.name, + User::class.java.`package`.name ) + + ObjectMapperFactory.objectMapper() + .registerModule(KotlinModule()) + .registerModule(IdTypesModule()) } @AfterClass @@ -228,4 +236,20 @@ class KotlinInteropTest { val numberOfMyNodes = sessionFactory.openSession().count(listOf(Filter("name", ComparisonOperator.EQUALS, "Brian"))) assertThat(numberOfMyNodes).isEqualTo(1) } + + @Test // GH-822 + fun `inline classes should work with dedicated serializers`() { + + val userId = StringID("aUserId") + val userToSave = User(userId, "Danger Dan"); + + sessionFactory.openSession().save(userToSave); + + val nameFilter = Filter("userId", ComparisonOperator.EQUALS, userId).ignoreCase() + val loadedUsers = sessionFactory.openSession().loadAll(User::class.java, nameFilter); + assertThat(loadedUsers).hasSize(1).extracting(User::userId).containsOnly(userId) + + val loadedUser = sessionFactory.openSession().queryForObject("MATCH (u:User) RETURN u", mapOf(Pair("userId", userId)))!! + assertThat(loadedUser).isNotNull().extracting(User::userId).isEqualTo(userId) + } } diff --git a/neo4j-ogm-tests/neo4j-ogm-integration-tests/src/test/kotlin/org/neo4j/ogm/kotlin/KotlinMetaDataTest.kt b/neo4j-ogm-tests/neo4j-ogm-integration-tests/src/test/kotlin/org/neo4j/ogm/kotlin/KotlinMetaDataTest.kt index 37f4bb324c..3cd55fc9f1 100644 --- a/neo4j-ogm-tests/neo4j-ogm-integration-tests/src/test/kotlin/org/neo4j/ogm/kotlin/KotlinMetaDataTest.kt +++ b/neo4j-ogm-tests/neo4j-ogm-integration-tests/src/test/kotlin/org/neo4j/ogm/kotlin/KotlinMetaDataTest.kt @@ -67,4 +67,13 @@ class KotlinMetaDataTest { assertThat(kotlinZoo.getFieldInfo("animals").typeDescriptor).isEqualTo("org.neo4j.ogm.domain.gh696.Animal") assertThat(kotlinZoo.getFieldInfo("zookeepers").typeDescriptor).isEqualTo("org.neo4j.ogm.domain.gh696.Zookeeper") } + + @Test // GH-822 + fun `OGM should detect the inlines class underlying value type`() { + val metaData = MetaData("org.neo4j.ogm.domain.gh822") + val userClassInfo = metaData.classInfo("User") + + assertThat(userClassInfo).isNotNull + assertThat(userClassInfo.getFieldInfo("userId").typeDescriptor).isEqualTo("java.lang.String") + } }