Skip to content

Commit

Permalink
Merge pull request #31 from neilg/jackson-message-adapter
Browse files Browse the repository at this point in the history
Add JacksonMessageAdapter
  • Loading branch information
zhxnlai authored Oct 19, 2018
2 parents 6735398 + ea1b918 commit 806ae72
Show file tree
Hide file tree
Showing 8 changed files with 293 additions and 1 deletion.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,5 @@ google-services.json
freeline.py
freeline/
freeline_project_description.json

.gradletasknamecache
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ implementation 'com.github.tinder.scarlet:scarlet:$latestVersion'
- [x] `moshi`
- [x] `gson`
- [x] `protobuf`
- [ ] `jackson`
- [x] `jackson`
- [ ] `simple-xml`

`StreamAdapter.Factory`
Expand Down
1 change: 1 addition & 0 deletions dependencies.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ ext {
gson = 'com.google.code.gson:gson:2.8.2'
protobuf = 'com.google.protobuf:protobuf-java:3.4.0'
jacksonDatabind = 'com.fasterxml.jackson.core:jackson-databind:2.9.5'
jacksonModuleKotlin = 'com.fasterxml.jackson.module:jackson-module-kotlin:2.9.5'
jsonPatch = 'com.github.fge:json-patch:1.9'

dagger = 'com.google.dagger:dagger:2.12'
Expand Down
2 changes: 2 additions & 0 deletions publish-jitpack.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@

./gradlew scarlet-message-adapter-gson:build scarlet-message-adapter-gson:publishToMavenLocal

./gradlew scarlet-message-adapter-jackson:build scarlet-message-adapter-jackson:publishToMavenLocal

./gradlew scarlet-stream-adapter-builtin:build scarlet-stream-adapter-builtin:publishToMavenLocal

./gradlew scarlet-stream-adapter-rxjava2:build scarlet-stream-adapter-rxjava2:publishToMavenLocal
Expand Down
77 changes: 77 additions & 0 deletions scarlet-message-adapter-jackson/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
apply plugin: 'kotlin'
apply plugin: 'java-library'
apply plugin: 'org.jetbrains.dokka'
apply plugin: 'maven-publish'

dependencies {
api rootProject.ext.jacksonDatabind
api kotlinReflect

implementation project(':scarlet-core')
implementation rootProject.ext.kotlinStdlib

testImplementation project(':scarlet')
testImplementation project(':scarlet-websocket-mockwebserver')
testImplementation project(':scarlet-test-utils')
testImplementation rootProject.ext.junit
testImplementation rootProject.ext.mockito
testImplementation rootProject.ext.kotlinReflect
testImplementation rootProject.ext.assertJ
testImplementation rootProject.ext.jacksonModuleKotlin
}

task sourcesJar(type: Jar, dependsOn: classes) {
classifier = 'sources'
from sourceSets.main.allSource
}

task dokkaJavadoc(type: org.jetbrains.dokka.gradle.DokkaTask) {
outputFormat = 'javadoc'
outputDirectory = javadoc.destinationDir

// impliedPlatforms = ['JVM']
// jdkVersion = 7

externalDocumentationLink {
url = new URL("https://docs.oracle.com/javase/7/docs/api/")
}
}

task javadocJar(type: Jar, dependsOn: dokkaJavadoc) {
classifier = 'javadoc'
from javadoc.destinationDir
}

artifacts {
archives sourcesJar, javadocJar
}

publishing {
publications {
mavenJava(MavenPublication) {
groupId 'com.tinder'
version version
artifactId project.getName()
artifact sourcesJar
artifact javadocJar
from components.java
}
}
}

artifactory {
contextUrl = 'https://tinder.jfrog.io/tinder'
publish {
repository {
repoKey = 'libs-release-local'
username = System.getenv("ARTIFACTORY_USER")
password = System.getenv("ARTIFACTORY_PASSWORD")
maven = true
}
defaults {
publications('mavenJava')
publishArtifacts = true
publishPom = true
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.tinder.scarlet.messageadapter.jackson

import com.fasterxml.jackson.databind.ObjectMapper
import com.tinder.scarlet.Message
import com.tinder.scarlet.MessageAdapter
import com.tinder.scarlet.utils.getRawType
import java.lang.reflect.Type

class JacksonMessageAdapter<T> private constructor(
private val objectMapper: ObjectMapper,
private val klass: Class<T>
) : MessageAdapter<T> {

override fun fromMessage(message: Message): T {
return when (message) {
is Message.Text -> objectMapper.readValue(message.value, klass)
is Message.Bytes -> objectMapper.readValue(message.value, klass)
}
}

override fun toMessage(data: T): Message {
val body = objectMapper.writeValueAsString(data)
return Message.Text(body)
}

class Factory constructor(
private val objectMapper: ObjectMapper = DEFAULT_OBJECT_MAPPER
) : MessageAdapter.Factory {
override fun create(type: Type, annotations: Array<Annotation>): MessageAdapter<*> =
JacksonMessageAdapter(objectMapper, type.getRawType())

companion object {
private val DEFAULT_OBJECT_MAPPER = ObjectMapper()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
package com.tinder.scarlet.messageadapter.jackson

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.KotlinModule
import com.tinder.scarlet.Lifecycle
import com.tinder.scarlet.Message
import com.tinder.scarlet.MessageAdapter
import com.tinder.scarlet.Scarlet
import com.tinder.scarlet.Stream
import com.tinder.scarlet.WebSocket.Event
import com.tinder.scarlet.WebSocket.Event.OnConnectionOpened
import com.tinder.scarlet.lifecycle.LifecycleRegistry
import com.tinder.scarlet.testutils.TestStreamObserver
import com.tinder.scarlet.testutils.any
import com.tinder.scarlet.testutils.containingText
import com.tinder.scarlet.testutils.test
import com.tinder.scarlet.websocket.mockwebserver.newWebSocketFactory
import com.tinder.scarlet.websocket.okhttp.newWebSocketFactory
import com.tinder.scarlet.ws.Receive
import com.tinder.scarlet.ws.Send
import okhttp3.OkHttpClient
import okhttp3.mockwebserver.MockWebServer
import org.assertj.core.api.Assertions.assertThat
import org.junit.Rule
import org.junit.Test
import java.lang.reflect.Type
import java.util.concurrent.TimeUnit

internal class JacksonMessageAdapterTest {

@get:Rule
private val mockWebServer = MockWebServer()
private val serverUrlString by lazy { mockWebServer.url("/").toString() }

private val serverLifecycleRegistry = LifecycleRegistry()
private lateinit var server: Service
private lateinit var serverEventObserver: TestStreamObserver<Event>

private val clientLifecycleRegistry = LifecycleRegistry()
private lateinit var client: Service
private lateinit var clientEventObserver: TestStreamObserver<Event>

@Test
fun sendAnInterface_shouldBeReceivedByTheServer() {
// Given
givenConnectionIsEstablished()
val data = AnImplementation("value")
val expectedString = """{"name":"value"}"""
val serverAnImplementationObserver = server.observeAnImplementation().test()

// When
val isSuccessful = client.sendAnInterface(data)

// Then
assertThat(isSuccessful).isTrue()
serverEventObserver.awaitValues(
any<Event.OnConnectionOpened<*>>(),
any<Event.OnMessageReceived>().containingText(expectedString)
)
serverAnImplementationObserver.awaitCount(1)
assertThat(serverAnImplementationObserver.values).containsExactly(data)
}

@Test
fun sendAnImplementation_shouldBeReceivedByTheServer() {
// Given
givenConnectionIsEstablished()
val data = AnImplementation("value")
val expectedString = """{"name":"value"}"""
val serverAnImplementationObserver = server.observeAnImplementation().test()

// When
val isSuccessful = client.sendAnImplementation(data)

// Then
assertThat(isSuccessful).isTrue()
serverEventObserver.awaitValues(
any<Event.OnConnectionOpened<*>>(),
any<Event.OnMessageReceived>().containingText(expectedString)
)
serverAnImplementationObserver.awaitCount(1)
assertThat(serverAnImplementationObserver.values).containsExactly(data)
}

private fun givenConnectionIsEstablished() {
createClientAndServer()
serverLifecycleRegistry.onNext(Lifecycle.State.Started)
clientLifecycleRegistry.onNext(Lifecycle.State.Started)
blockUntilConnectionIsEstablish()
}

private fun createClientAndServer() {
val factory = JacksonMessageAdapter.Factory(createJackson())
server = createServer(factory)
serverEventObserver = server.observeEvents().test()
client = createClient(factory)
clientEventObserver = client.observeEvents().test()
}

private fun createJackson(): ObjectMapper = ObjectMapper()
.registerModule(KotlinModule())

private fun createServer(factory: JacksonMessageAdapter.Factory): Service {
val webSocketFactory = mockWebServer.newWebSocketFactory()
val scarlet = Scarlet.Builder()
.webSocketFactory(webSocketFactory)
.addMessageAdapterFactory(factory)
.lifecycle(serverLifecycleRegistry)
.build()
return scarlet.create()
}

private fun createClient(factory: JacksonMessageAdapter.Factory): Service {
val okHttpClient = OkHttpClient.Builder()
.writeTimeout(500, TimeUnit.MILLISECONDS)
.readTimeout(500, TimeUnit.MILLISECONDS)
.build()
val webSocketFactory = okHttpClient.newWebSocketFactory(serverUrlString)
val scarlet = Scarlet.Builder()
.webSocketFactory(webSocketFactory)
.addMessageAdapterFactory(TextMessageAdapter.Factory())
.addMessageAdapterFactory(factory)
.lifecycle(clientLifecycleRegistry)
.build()
return scarlet.create()
}

private fun blockUntilConnectionIsEstablish() {
serverEventObserver.awaitValues(
any<OnConnectionOpened<*>>()
)
clientEventObserver.awaitValues(
any<OnConnectionOpened<*>>()
)
}

companion object {

interface AnInterface {
val name: String?
}

data class AnImplementation(override val name: String?) : AnInterface

internal class TextMessageAdapter : MessageAdapter<String> {

override fun fromMessage(message: Message): String = (message as Message.Text).value

override fun toMessage(data: String): Message = Message.Text(data)

class Factory : MessageAdapter.Factory {
override fun create(type: Type, annotations: Array<Annotation>): MessageAdapter<*> = when (type) {
String::class.java -> TextMessageAdapter()
else -> throw IllegalArgumentException("$type is not supported.")
}
}
}

internal interface Service {
@Receive
fun observeEvents(): Stream<Event>

@Send
fun sendAnImplementation(impl: AnImplementation): Boolean

@Receive
fun observeAnImplementation(): Stream<AnImplementation>

@Send
fun sendAnInterface(impl: AnInterface): Boolean
}
}
}
1 change: 1 addition & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ include ':scarlet-websocket-mockwebserver'
include ':scarlet-message-adapter-moshi'
include ':scarlet-message-adapter-protobuf'
include ':scarlet-message-adapter-gson'
include ':scarlet-message-adapter-jackson'
include ':scarlet-stream-adapter-rxjava'
include ':scarlet-stream-adapter-rxjava2'
include ':scarlet-stream-adapter-coroutines'
Expand Down

0 comments on commit 806ae72

Please sign in to comment.