Commit 439f439a authored by Patrick Schlindwein's avatar Patrick Schlindwein
Browse files

Feat/#8 implement ktor business logic

parent 22bc4d53
...@@ -7,3 +7,5 @@ src/ktor-server/.idea/compiler.xml ...@@ -7,3 +7,5 @@ src/ktor-server/.idea/compiler.xml
src/ktor-server/.idea/gradle.xml src/ktor-server/.idea/gradle.xml
src/ktor-server/.idea/ktor-server.iml src/ktor-server/.idea/ktor-server.iml
src/ktor-server/.idea/misc.xml src/ktor-server/.idea/misc.xml
.idea/
.gradle/
\ No newline at end of file
...@@ -5,4 +5,5 @@ ...@@ -5,4 +5,5 @@
.idea/**/usage.statistics.xml .idea/**/usage.statistics.xml
.idea/**/dictionaries .idea/**/dictionaries
.idea/**/shelf .idea/**/shelf
.idea
build build
...@@ -17,11 +17,16 @@ repositories { ...@@ -17,11 +17,16 @@ repositories {
} }
dependencies { dependencies {
implementation "io.ktor:ktor-gson:$ktor_version"
implementation "io.ktor:ktor-server-core:$ktor_version" implementation "io.ktor:ktor-server-core:$ktor_version"
implementation "io.ktor:ktor-server-netty:$ktor_version" implementation "io.ktor:ktor-server-netty:$ktor_version"
implementation "io.ktor:ktor-serialization:$ktor_version" implementation "io.ktor:ktor-serialization:$ktor_version"
implementation "org.jetbrains.kotlinx:kotlinx-serialization-core:$kotlin_serialization" implementation "org.jetbrains.kotlinx:kotlinx-serialization-core:$kotlin_serialization"
implementation 'com.squareup.retrofit2:retrofit:2.7.0'
implementation 'com.squareup.retrofit2:converter-gson:2.7.0'
implementation 'com.squareup.okhttp3:okhttp:4.3.1'
// tests // tests
testImplementation(platform('org.junit:junit-bom:5.7.1')) testImplementation(platform('org.junit:junit-bom:5.7.1'))
testImplementation('org.junit.jupiter:junit-jupiter') testImplementation('org.junit.jupiter:junit-jupiter')
...@@ -51,6 +56,10 @@ dependencies { ...@@ -51,6 +56,10 @@ dependencies {
// for html parsing or scraping // for html parsing or scraping
// https://mvnrepository.com/artifact/org.jsoup/jsoup // https://mvnrepository.com/artifact/org.jsoup/jsoup
implementation group: 'org.jsoup', name: 'jsoup', version: '1.13.1' implementation group: 'org.jsoup', name: 'jsoup', version: '1.13.1'
testImplementation 'com.nhaarman.mockitokotlin2:mockito-kotlin:2.1.0' //Mockito-Kotlin
testImplementation 'org.mockito:mockito-inline:2.21.0'
testImplementation 'org.hamcrest:hamcrest:2.2'
} }
compileKotlin { compileKotlin {
......
...@@ -7,6 +7,7 @@ import io.ktor.features.* ...@@ -7,6 +7,7 @@ import io.ktor.features.*
import io.ktor.response.* import io.ktor.response.*
import io.ktor.routing.* import io.ktor.routing.*
import io.ktor.serialization.* import io.ktor.serialization.*
import kotlinx.serialization.json.Json
import registerUploadRoutes import registerUploadRoutes
...@@ -14,12 +15,15 @@ fun main(args: Array<String>): Unit = io.ktor.server.netty.EngineMain.main(args) ...@@ -14,12 +15,15 @@ fun main(args: Array<String>): Unit = io.ktor.server.netty.EngineMain.main(args)
fun Application.module() { fun Application.module() {
install(ContentNegotiation) { install(ContentNegotiation) {
json() json(Json {
prettyPrint = true
})
} }
routing { routing {
get("/summary") { get("/summary") {
val response = PythonBridge().getSummary("test bridge") val response = PythonBridge().getSummary("test bridge")
call.respondText(response) call.respond(response)
} }
get("/") { get("/") {
call.respondText("IntentFinder is available") call.respondText("IntentFinder is available")
......
package de.h_da.fbi.smebt.intentfinder.server.nlp package de.h_da.fbi.smebt.intentfinder.server.nlp
import io.ktor.client.* import de.h_da.fbi.smebt.intentfinder.server.nlp.client.RetrofitClient
import io.ktor.client.engine.cio.* import de.h_da.fbi.smebt.intentfinder.server.nlp.dto.Summary
import io.ktor.client.request.* import java.util.*
import io.ktor.client.request.forms.* import java.util.concurrent.ExecutionException
import io.ktor.client.statement.* import java.util.concurrent.ExecutorCompletionService
import io.ktor.http.* import java.util.concurrent.Executors
import io.ktor.http.content.* import kotlin.collections.ArrayList
class PythonBridge:AutoCloseable { class PythonBridge(private val client: RetrofitClient = RetrofitClient()) {
private val httpClient = HttpClient(CIO)
companion object { private fun getAllSummaries(text: String): List<Summary> {
const val NLP_ENDPOINT = "http://127.0.0.1:8000" val strategies = client.getStrategies()
}
val executor = Executors.newFixedThreadPool(strategies.size)
val completionService = ExecutorCompletionService<Summary>(executor)
strategies.forEach {
completionService.submit { client.getSummary(it.name, text) }
}
val summaries = ArrayList<Summary>()
repeat(strategies.size) {
try {
val result = completionService.take()
summaries.add(result.get())
} catch (ex: ExecutionException) {
// TODO handle ExecutionException
}
}
suspend fun getSummary(text:String): String{ return summaries
// options see https://ktor.io/docs/response.html#receive and https://kotlinlang.org/docs/mobile/use-ktor-for-networking.html
val response = httpClient.post<HttpResponse> (NLP_ENDPOINT+ "/summarize-text?text=$text")
return response.readText()
} }
suspend fun getIntentId(text:String): String { fun getSummary(text: String): List<Summary> {
val response = httpClient.post<HttpResponse> (NLP_ENDPOINT+ "/intentid?text=$text") return getAllSummaries(text).sortedWith(compareByDescending { it.quality })
return response.readText()
} }
override fun close() { fun getIntentId(text: String): String {
httpClient.close() return client.getIntentId(text)
} }
} }
\ No newline at end of file
package de.h_da.fbi.smebt.intentfinder.server.nlp.client
import com.google.gson.JsonObject
import de.h_da.fbi.smebt.intentfinder.server.nlp.dto.Strategy
import de.h_da.fbi.smebt.intentfinder.server.nlp.dto.Summary
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
class RetrofitClient {
companion object {
private const val BASE_URL = "http://127.0.0.1:8000"
}
private var client: SummarizationAPI = buildRetrofitClient()
private fun buildRetrofitClient(): SummarizationAPI {
val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(OkHttpClient.Builder().build())
.build()
return retrofit.create(SummarizationAPI::class.java)
}
fun root() : JsonObject {
return RetrofitExecutor().execute(client.root())
}
fun getStrategies() : List<Strategy> {
return RetrofitExecutor().execute(client.getStrategies())
}
fun getSummary(strategy: String, text: String) : Summary {
return RetrofitExecutor().execute(client.getSummary(strategy, text))
}
fun getIntentId(text: String) : String {
return RetrofitExecutor().execute(client.getIntentId(text))
}
}
\ No newline at end of file
package de.h_da.fbi.smebt.intentfinder.server.nlp.client
import retrofit2.Call
class RetrofitExecutor {
fun <T> execute(call: Call<T>): T {
try {
val response = call.execute()
if (response.isSuccessful) {
return response.body()!!
} else {
throw NullPointerException("call was not successful")
}
} catch (e: NullPointerException) {
// TODO Body null but response successful? better handling
throw e
}
}
}
\ No newline at end of file
package de.h_da.fbi.smebt.intentfinder.server.nlp.client
import com.google.gson.JsonObject
import de.h_da.fbi.smebt.intentfinder.server.nlp.dto.Strategy
import de.h_da.fbi.smebt.intentfinder.server.nlp.dto.Summary
import retrofit2.Call
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.POST
import retrofit2.http.Path
interface SummarizationAPI {
@GET("/")
fun root() : Call<JsonObject>
@GET("/strategies")
fun getStrategies(): Call<List<Strategy>>
@POST("/summarize/{strategy}")
fun getSummary(@Path("strategy") strategy: String, @Body text: String): Call<Summary>
@POST("/intentid")
fun getIntentId(@Body text: String) : Call<String>
}
\ No newline at end of file
package de.h_da.fbi.smebt.intentfinder.server.nlp.dto
import kotlinx.serialization.Serializable
@Serializable
data class Strategy(val name: String)
package de.h_da.fbi.smebt.intentfinder.server.nlp.dto
import kotlinx.serialization.Serializable
@Serializable
data class Summary(
val strategy: String,
val quality: Double,
val result: String,
)
package de.h_da.fbi.smebt.intentfinder.server.sources
import de.h_da.fbi.smebt.intentfinder.server.nlp.PythonBridge
import de.h_da.fbi.smebt.intentfinder.server.nlp.client.RetrofitClient
import de.h_da.fbi.smebt.intentfinder.server.nlp.dto.Strategy
import de.h_da.fbi.smebt.intentfinder.server.nlp.dto.Summary
import org.hamcrest.CoreMatchers
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.collection.IsIterableContainingInAnyOrder
import org.hamcrest.collection.IsIterableContainingInOrder
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import org.mockito.Mockito.*
import kotlin.test.assertEquals
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class PythonBridgeTest {
lateinit var retrofitClient: RetrofitClient
lateinit var bridge: PythonBridge
@BeforeAll
fun setup() {
retrofitClient = mock(RetrofitClient::class.java)
bridge = PythonBridge(retrofitClient)
}
@Test
fun `returns strategy with the highest quality`() {
val strategyNameOne = "first"
val strategyNameTwo = "second"
val strategies = listOf(
Strategy(strategyNameOne),
Strategy(strategyNameTwo)
)
val summaryOne = Summary(strategyNameOne, 0.7, "")
val summaryTwo = Summary(strategyNameTwo, 0.8, "")
`when`(retrofitClient.getStrategies()).thenReturn(strategies)
`when`(retrofitClient.getSummary(strategyNameOne, "")).thenReturn(summaryOne)
`when`(retrofitClient.getSummary(strategyNameTwo, "")).thenReturn(summaryTwo)
val result = bridge.getSummary("")
verify(retrofitClient, times(1)).getStrategies()
verify(retrofitClient, times(2)).getSummary(anyString(), anyString())
assertEquals(2, result.size)
assertThat(result, CoreMatchers.`is`(listOf(summaryTwo, summaryOne)))
}
}
\ No newline at end of file
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment