-
Notifications
You must be signed in to change notification settings - Fork 240
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #31 from neilg/jackson-message-adapter
Add JacksonMessageAdapter
- Loading branch information
Showing
8 changed files
with
293 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -62,3 +62,5 @@ google-services.json | |
freeline.py | ||
freeline/ | ||
freeline_project_description.json | ||
|
||
.gradletasknamecache |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} | ||
} |
36 changes: 36 additions & 0 deletions
36
...-jackson/src/main/java/com/tinder/scarlet/messageadapter/jackson/JacksonMessageAdapter.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
} | ||
} | ||
} |
173 changes: 173 additions & 0 deletions
173
...kson/src/test/java/com/tinder/scarlet/messageadapter/jackson/JacksonMessageAdapterTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters