|
@@ -0,0 +1,168 @@
|
|
|
+package kooby
|
|
|
+
|
|
|
+import com.fasterxml.jackson.databind.ObjectMapper
|
|
|
+import io.jooby.BadRequestException
|
|
|
+import io.jooby.Context
|
|
|
+import io.jooby.ExecutionMode.EVENT_LOOP
|
|
|
+import io.jooby.MediaType.JSON
|
|
|
+import io.jooby.hikari.HikariModule
|
|
|
+import io.jooby.json.JacksonModule
|
|
|
+import io.jooby.require
|
|
|
+import io.jooby.rocker.Rockerby
|
|
|
+import io.jooby.runApp
|
|
|
+import java.util.*
|
|
|
+import java.util.concurrent.ThreadLocalRandom
|
|
|
+import javax.sql.DataSource
|
|
|
+
|
|
|
+private const val SELECT_WORLD = "select * from world where id=?"
|
|
|
+
|
|
|
+private const val DB_ROWS = 10000
|
|
|
+
|
|
|
+private const val MESSAGE = "Hello, World!"
|
|
|
+
|
|
|
+private val MESSAGE_BYTES = MESSAGE.toByteArray(Charsets.UTF_8)
|
|
|
+
|
|
|
+
|
|
|
+data class Message(val message: String = MESSAGE)
|
|
|
+
|
|
|
+class World(val id: Int, var randomNumber: Int)
|
|
|
+
|
|
|
+data class Fortune(val id: Int, var message: String)
|
|
|
+
|
|
|
+fun main(args: Array<String>) {
|
|
|
+ runApp(args, EVENT_LOOP) {
|
|
|
+
|
|
|
+ /** Server options (netty only): */
|
|
|
+ serverOptions {
|
|
|
+ singleLoop = true
|
|
|
+ }
|
|
|
+
|
|
|
+ /** JSON: */
|
|
|
+ install(JacksonModule())
|
|
|
+ val mapper = require(ObjectMapper::class)
|
|
|
+
|
|
|
+ /** Database: */
|
|
|
+ install(HikariModule())
|
|
|
+ val ds = require(DataSource::class)
|
|
|
+
|
|
|
+ /** Template engine: */
|
|
|
+ install(Rockerby())
|
|
|
+
|
|
|
+ get("/plaintext") {
|
|
|
+ ctx.send(MESSAGE_BYTES)
|
|
|
+ }
|
|
|
+
|
|
|
+ get("/json") {
|
|
|
+ ctx.setResponseType(JSON)
|
|
|
+ ctx.send(mapper.writeValueAsBytes(Message()))
|
|
|
+ }
|
|
|
+
|
|
|
+ /** Go blocking : */
|
|
|
+ dispatch {
|
|
|
+
|
|
|
+ /** Single query: */
|
|
|
+ get("/db") {
|
|
|
+ val rnd = ThreadLocalRandom.current()
|
|
|
+ val result = ds.connection.use { conn ->
|
|
|
+ conn.prepareStatement(SELECT_WORLD).use { statement ->
|
|
|
+ statement.setInt(1, nextRandom(rnd))
|
|
|
+ statement.executeQuery().use { rs ->
|
|
|
+ rs.next()
|
|
|
+ World(rs.getInt(1), rs.getInt(2))
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ctx.setResponseType(JSON)
|
|
|
+ ctx.send(mapper.writeValueAsBytes(result))
|
|
|
+ }
|
|
|
+
|
|
|
+ /** Multiple query: */
|
|
|
+ get("/queries") {
|
|
|
+ val rnd = ThreadLocalRandom.current()
|
|
|
+ val queries = ctx.queries()
|
|
|
+ val result = ArrayList<World>(queries)
|
|
|
+ ds.connection.use { conn ->
|
|
|
+ conn.prepareStatement(SELECT_WORLD).use { statement ->
|
|
|
+ for (i in 1..queries) {
|
|
|
+ statement.setInt(1, nextRandom(rnd))
|
|
|
+ statement.executeQuery().use { rs ->
|
|
|
+ while (rs.next()) {
|
|
|
+ result += World(rs.getInt(1), rs.getInt(2))
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ctx.setResponseType(JSON)
|
|
|
+ ctx.send(mapper.writeValueAsBytes(result))
|
|
|
+ }
|
|
|
+
|
|
|
+ /** Updates: */
|
|
|
+ get("/updates") {
|
|
|
+ val queries = ctx.queries()
|
|
|
+ val result = ArrayList<World>(queries)
|
|
|
+ val rnd = ThreadLocalRandom.current()
|
|
|
+
|
|
|
+ val updateSql = StringJoiner(
|
|
|
+ ", ",
|
|
|
+ "UPDATE world SET randomNumber = temp.randomNumber FROM (VALUES ",
|
|
|
+ " ORDER BY 1) AS temp(id, randomNumber) WHERE temp.id = world.id")
|
|
|
+
|
|
|
+ ds.connection.use { connection ->
|
|
|
+ connection.prepareStatement(SELECT_WORLD).use { statement ->
|
|
|
+ for (i in 1..queries) {
|
|
|
+ statement.setInt(1, nextRandom(rnd))
|
|
|
+ statement.executeQuery().use { rs ->
|
|
|
+ rs.next()
|
|
|
+ result += World(rs.getInt("id"), rs.getInt("randomNumber"))
|
|
|
+ }
|
|
|
+ // prepare update query
|
|
|
+ updateSql.add("(?, ?)")
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ connection.prepareStatement(updateSql.toString()).use { statement ->
|
|
|
+ var i = 0
|
|
|
+ for (world in result) {
|
|
|
+ world.randomNumber = nextRandom(rnd)
|
|
|
+ statement.setInt(++i, world.id)
|
|
|
+ statement.setInt(++i, world.randomNumber)
|
|
|
+ }
|
|
|
+ statement.executeUpdate()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ctx.setResponseType(JSON)
|
|
|
+ ctx.send(mapper.writeValueAsBytes(result))
|
|
|
+ }
|
|
|
+
|
|
|
+ /** Fortunes: */
|
|
|
+ get("/fortunes") {
|
|
|
+ val fortunes = ArrayList<Fortune>()
|
|
|
+ ds.connection.use { connection ->
|
|
|
+ connection.prepareStatement("select * from fortune").use { stt ->
|
|
|
+ stt.executeQuery().use { rs ->
|
|
|
+ while (rs.next()) {
|
|
|
+ fortunes += Fortune(rs.getInt("id"), rs.getString("message"))
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ fortunes.add(Fortune(0, "Additional fortune added at request time."))
|
|
|
+ fortunes.sortBy { it.message }
|
|
|
+
|
|
|
+ /** render view: */
|
|
|
+ views.fortunes.template(fortunes)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+private fun nextRandom(rnd: Random): Int {
|
|
|
+ return rnd.nextInt(DB_ROWS) + 1
|
|
|
+}
|
|
|
+
|
|
|
+fun Context.queries() = try {
|
|
|
+ this.query("queries").intValue(1).coerceIn(1, 500)
|
|
|
+} catch (x: BadRequestException) {
|
|
|
+ 1
|
|
|
+}
|