Commit cea0320d authored by Patrick Schlindwein's avatar Patrick Schlindwein
Browse files

Merge branch 'feat/#8_implement_ktor_business_logic' into 'master'

Feat/#8 implement ktor business logic

See merge request !20
parents 22bc4d53 439f439a
Pipeline #69527 passed with stages
in 9 minutes and 52 seconds
......@@ -7,3 +7,5 @@ src/ktor-server/.idea/compiler.xml
src/ktor-server/.idea/gradle.xml
src/ktor-server/.idea/ktor-server.iml
src/ktor-server/.idea/misc.xml
.idea/
.gradle/
\ No newline at end of file
......@@ -5,4 +5,5 @@
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
.idea
build
......@@ -17,11 +17,16 @@ repositories {
}
dependencies {
implementation "io.ktor:ktor-gson:$ktor_version"
implementation "io.ktor:ktor-server-core:$ktor_version"
implementation "io.ktor:ktor-server-netty:$ktor_version"
implementation "io.ktor:ktor-serialization:$ktor_version"
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
testImplementation(platform('org.junit:junit-bom:5.7.1'))
testImplementation('org.junit.jupiter:junit-jupiter')
......@@ -51,6 +56,10 @@ dependencies {
// for html parsing or scraping
// https://mvnrepository.com/artifact/org.jsoup/jsoup
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 {
......
......@@ -7,6 +7,7 @@ import io.ktor.features.*
import io.ktor.response.*
import io.ktor.routing.*
import io.ktor.serialization.*
import kotlinx.serialization.json.Json
import registerUploadRoutes
......@@ -14,12 +15,15 @@ fun main(args: Array<String>): Unit = io.ktor.server.netty.EngineMain.main(args)
fun Application.module() {
install(ContentNegotiation) {
json()
json(Json {
prettyPrint = true
})
}
routing {
get("/summary") {
val response = PythonBridge().getSummary("test bridge")
call.respondText(response)
call.respond(response)
}
get("/") {
call.respondText("IntentFinder is available")
......
package de.h_da.fbi.smebt.intentfinder.server.nlp
import io.ktor.client.*
import io.ktor.client.engine.cio.*
import io.ktor.client.request.*
import io.ktor.client.request.forms.*
import io.ktor.client.statement.*
import io.ktor.http.*
import io.ktor.http.content.*
class PythonBridge:AutoCloseable {
private val httpClient = HttpClient(CIO)
companion object {
const val NLP_ENDPOINT = "http://127.0.0.1:8000"
}
import de.h_da.fbi.smebt.intentfinder.server.nlp.client.RetrofitClient
import de.h_da.fbi.smebt.intentfinder.server.nlp.dto.Summary
import java.util.*
import java.util.concurrent.ExecutionException
import java.util.concurrent.ExecutorCompletionService
import java.util.concurrent.Executors
import kotlin.collections.ArrayList
class PythonBridge(private val client: RetrofitClient = RetrofitClient()) {
private fun getAllSummaries(text: String): List<Summary> {
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{
// 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()
return summaries
}
suspend fun getIntentId(text:String): String {
val response = httpClient.post<HttpResponse> (NLP_ENDPOINT+ "/intentid?text=$text")
return response.readText()
fun getSummary(text: String): List<Summary> {
return getAllSummaries(text).sortedWith(compareByDescending { it.quality })
}
override fun close() {
httpClient.close()
fun getIntentId(text: String): String {
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
Markdown is supported
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