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

DOCSP-36217: polymorphic serialization #163

Merged
merged 6 commits into from
Jul 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ dependencies {
implementation("com.github.luben:zstd-jni:1.5.5-4")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:1.5.1")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0")
implementation("org.mongodb:bson-kotlinx:4.10.0-alpha1")
implementation("org.mongodb:bson-kotlinx:5.1.1")
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0")
}

Expand Down
2 changes: 1 addition & 1 deletion examples/src/test/kotlin/CodecTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ internal class CodecTest {

init {
powerStatusCodec = registry[PowerStatus::class.java]
integerCodec = registry.get(Int::class.java)
integerCodec = IntegerCodec()
}

override fun encode(writer: BsonWriter, value: Monolight, encoderContext: EncoderContext) {
Expand Down
64 changes: 64 additions & 0 deletions examples/src/test/kotlin/KotlinXSerializationTest.kt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@

import com.mongodb.client.model.Filters
import com.mongodb.client.model.Filters.eq
import com.mongodb.kotlin.client.coroutine.MongoClient
import config.getConfig
Expand Down Expand Up @@ -35,6 +36,7 @@ import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import java.util.Date
import kotlin.test.assertTrue

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
internal class KotlinXSerializationTest {
Expand Down Expand Up @@ -178,5 +180,67 @@ internal class KotlinXSerializationTest {
collection.drop()
}

// :snippet-start: polymorphic-dataclasses
@Serializable
sealed interface Person {
val name: String
}

@Serializable
data class Student(
@Contextual
@SerialName("_id")
val id: ObjectId,
override val name: String,
val grade: Int,
) : Person

@Serializable
data class Teacher(
@Contextual
@SerialName("_id")
val id: ObjectId,
override val name: String,
val department: String,
) : Person
// :snippet-end:

@Test
fun polymorphicSerializationTest() = runBlocking {

// :snippet-start: polymorphic-example
val collection = database.getCollection<Person>("school")

val teacherDoc = Teacher(ObjectId(), "Vivian Lee", "History")
val studentDoc = Student(ObjectId(), "Kate Parker", 10)

collection.insertOne(teacherDoc)
collection.insertOne(studentDoc)

println("Retrieving by using data classes")
collection.withDocumentClass<Teacher>()
.find(Filters.exists("department"))
.first().also { println(it) }

collection.withDocumentClass<Student>()
.find(Filters.exists("grade"))
.first().also { println(it) }

println("\nRetrieving by using Person interface")
val resultsFlow = collection.withDocumentClass<Person>().find()
resultsFlow.collect { println(it) }

println("\nRetrieving as Document type")
val resultsDocFlow = collection.withDocumentClass<Document>().find()
resultsDocFlow.collect { println(it) }
// :snippet-end:

if (resultsFlow != null) {
assertTrue(resultsFlow.firstOrNull() is Teacher)
}

collection.drop()
}

}

21 changes: 0 additions & 21 deletions examples/src/test/kotlin/TlsTest.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@

import com.mongodb.ConnectionString
import com.mongodb.MongoClientSettings
import com.mongodb.connection.netty.NettyStreamFactoryFactory
import com.mongodb.kotlin.client.coroutine.MongoClient
import config.getConfig
import io.netty.handler.ssl.SslContextBuilder
Expand Down Expand Up @@ -87,25 +86,5 @@ internal class TlsTest {
assertEquals(sslContext, settings.sslSettings.context)
}

@Test
fun nettyTlsConfigurationTest() {
// :snippet-start: netty-tls-configuration
val sslContext = SslContextBuilder.forClient()
.sslProvider(SslProvider.OPENSSL)
.build()
val settings = MongoClientSettings.builder()
.applyToSslSettings { builder -> builder.enabled(true) }
.streamFactoryFactory(
NettyStreamFactoryFactory.builder()
.sslContext(sslContext)
.build()
)
.build()
val mongoClient = MongoClient.create(settings)
// :snippet-end:
mongoClient.close()
assertEquals(true, settings.sslSettings.isEnabled)
assertEquals(true, settings.streamFactoryFactory is NettyStreamFactoryFactory)
}
}
// :replace-end:
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ class MonolightCodec(registry: CodecRegistry) : Codec<Monolight> {

init {
powerStatusCodec = registry[PowerStatus::class.java]
integerCodec = registry.get(Int::class.java)
integerCodec = IntegerCodec()
}

override fun encode(writer: BsonWriter, value: Monolight, encoderContext: EncoderContext) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
@Serializable
sealed interface Person {
val name: String
}

@Serializable
data class Student(
@Contextual
@SerialName("_id")
val id: ObjectId,
override val name: String,
val grade: Int,
) : Person

@Serializable
data class Teacher(
@Contextual
@SerialName("_id")
val id: ObjectId,
override val name: String,
val department: String,
) : Person
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
val collection = database.getCollection<Person>("school")

val teacherDoc = Teacher(ObjectId(), "Vivian Lee", "History")
val studentDoc = Student(ObjectId(), "Kate Parker", 10)

collection.insertOne(teacherDoc)
collection.insertOne(studentDoc)

println("Retrieving by using data classes")
collection.withDocumentClass<Teacher>()
.find(Filters.exists("department"))
.first().also { println(it) }

collection.withDocumentClass<Student>()
.find(Filters.exists("grade"))
.first().also { println(it) }

println("\nRetrieving by using Person interface")
val resultsFlow = collection.withDocumentClass<Person>().find()
resultsFlow.collect { println(it) }

println("\nRetrieving as Document type")
val resultsDocFlow = collection.withDocumentClass<Document>().find()
resultsDocFlow.collect { println(it) }
30 changes: 0 additions & 30 deletions source/fundamentals/connection/tls.txt
Original file line number Diff line number Diff line change
Expand Up @@ -239,36 +239,6 @@ object to the builder in the ``applyToSslSettings()`` lambda:
.. literalinclude:: /examples/generated/TlsTest.snippet.custom-tls-configuration.kt
:language: kotlin

Customize TLS/SSL Configuration through the Netty SslContext
------------------------------------------------------------

If you use the driver with `Netty <https://netty.io/>`__ for network IO,
you have an option to plug an alternative TLS/SSL protocol implementation
provided by Netty.

.. code-block:: kotlin
:copyable: true

import com.mongodb.MongoClientSettings
import com.mongodb.client.MongoClient
import com.mongodb.connection.netty.NettyStreamFactoryFactory
import io.netty.handler.ssl.SslContext
import io.netty.handler.ssl.SslContextBuilder
import io.netty.handler.ssl.SslProvider

.. note::

The driver tests with Netty version ``{+nettyVersion+}``

To instruct the driver to use `io.netty.handler.ssl.SslContext <https://netty.io/4.1/api/io/netty/handler/ssl/SslContext.html>`__,
use the `NettyStreamFactoryFactory.Builder.sslContext <{+api+}/apidocs/mongodb-driver-core/com/mongodb/connection/netty/NettyStreamFactoryFactory.Builder.html#sslContext(io.netty.handler.ssl.SslContext)>`__
method. See the method documentation for details about the different `io.netty.handler.ssl.SslProvider <https://netty.io/4.1/api/io/netty/handler/ssl/SslProvider.html>`__
variants the driver supports and the implications of using them.

.. literalinclude:: /examples/generated/TlsTest.snippet.netty-tls-configuration.kt
:language: kotlin


Online Certificate Status Protocol (OCSP)
-----------------------------------------

Expand Down
57 changes: 56 additions & 1 deletion source/fundamentals/data-formats/serialization.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Kotlin Serialization
:values: reference

.. meta::
:keywords: code example, data model, conversion
:keywords: code example, data model, conversion, polymorphism

.. contents:: On this page
:local:
Expand Down Expand Up @@ -98,6 +98,8 @@ dependencies to your project by using the :guilabel:`Gradle` and
<version>{+full-version+}</version>
</dependency>

.. _kotlin-data-class-annotation:

Annotate Data Classes
---------------------

Expand Down Expand Up @@ -246,3 +248,56 @@ see the following API documentation:
- `KotlinSerializerCodec.create() <{+api+}/apidocs/bson-kotlinx/bson-kotlinx/org.bson.codecs.kotlinx/-kotlin-serializer-codec/-companion/create.html>`__
- `BsonConfiguration <{+api+}/apidocs/bson-kotlinx/bson-kotlinx/org.bson.codecs.kotlinx/-bson-configuration/index.html>`__

.. _kotlin-polymorphic:

Polymorphic Serialization
-------------------------

The {+driver-short+} natively supports serialization and deserialization
of polymorphic classes. When you mark a sealed interface and data
classes that inherit that interface with the ``@Serializable``
annotation, the driver uses a ``KSerializer`` implementation to handle
conversion of your types to and from BSON.

When you insert an instance of a polymorphic data class into MongoDB,
the driver adds the field ``_t``, the
discriminator field. The value of this field is the data class name.

Polymorphic Data Classes Example
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The following example creates an interface and two data classes that
inherit that interface. In the data classes, the ``id`` field is marked
with the annotations described in the
:ref:`kotlin-data-class-annotation` section:

.. literalinclude:: /examples/generated/KotlinXSerializationTest.snippet.polymorphic-dataclasses.kt
:language: kotlin

Then, you can perform operations with data classes as usual. The
following example parametrizes the collection with the ``Person``
interface, then performs operations with the polymorphic classes
``Teacher`` and ``Student``. When you retrieve documents, the driver
automatically detects the type based on the discriminator value and
deserializes them accordingly.

.. io-code-block::
:copyable: true

.. input:: /examples/generated/KotlinXSerializationTest.snippet.polymorphic-example.kt
:language: kotlin

.. output::
:language: console

Retrieving by using data classes
Teacher(id=..., name=Vivian Lee, department=History)
Student(id=..., name=Kate Parker, grade=10)

Retrieving by using Person interface
Teacher(id=..., name=Vivian Lee, department=History)
Student(id=..., name=Kate Parker, grade=10)

Retrieving as Document type
Document{{_id=..., _t=Teacher, name=Vivian Lee, department=History}}
Document{{_id=..., _t=Student, name=Kate Parker, grade=10}}
5 changes: 2 additions & 3 deletions source/whats-new.txt
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,8 @@ Improvements in 5.1
New Features in 5.1
~~~~~~~~~~~~~~~~~~~

- Support for polymorphic serialization.

.. TODO add polymorphic serialization content
- Support for polymorphic serialization. To learn more, see the
:ref:`kotlin-polymorphic` section of the Kotlin Serialization guide.

- Introduces the ``serverMonitoringMode`` connection URI option. To
learn more, see the :ref:`connection-options` guide.
Expand Down
Loading