|
package io.abner.vertxkotlin |
|
|
|
import io.vertx.core.http.HttpServer |
|
import io.vertx.ext.web.Route |
|
import io.vertx.ext.web.Router |
|
import io.vertx.ext.web.RoutingContext |
|
import io.vertx.kotlin.coroutines.CoroutineVerticle |
|
import io.vertx.kotlin.coroutines.awaitResult |
|
import io.vertx.kotlin.core.json.* |
|
import io.vertx.kotlin.coroutines.dispatcher |
|
import kotlinx.coroutines.experimental.launch |
|
|
|
import io.reactiverse.reactivex.pgclient.PgConnection |
|
import io.reactiverse.reactivex.pgclient.PgClient |
|
import io.reactiverse.reactivex.pgclient.PgPool |
|
import io.reactiverse.reactivex.pgclient.PgRowSet |
|
import io.reactiverse.reactivex.pgclient.Tuple |
|
|
|
import io.reactiverse.pgclient.PgPoolOptions |
|
|
|
/* build.gradle |
|
|
|
buildscript { |
|
ext { |
|
kotlin_version = '1.2.71' |
|
vertx_version = '3.5.4' |
|
slf4j_version = '1.7.21' |
|
hsqldb_version = '2.3.4' |
|
reactivepgclient_version = '0.10.6' |
|
} |
|
|
|
repositories { |
|
jcenter() |
|
} |
|
|
|
dependencies { |
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" |
|
} |
|
} |
|
|
|
plugins { |
|
id 'java' |
|
id 'application' |
|
id 'com.github.johnrengelman.shadow' version '1.2.4' |
|
} |
|
|
|
apply plugin: 'kotlin' |
|
|
|
sourceCompatibility = 1.8 |
|
targetCompatibility = 1.8 |
|
|
|
mainClassName = 'io.vertx.core.Launcher' |
|
def mainVerticleName = 'io.abner.vertxkotlin.MainVerticle' |
|
|
|
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { |
|
kotlinOptions { |
|
jvmTarget = "1.8" |
|
} |
|
} |
|
|
|
repositories { |
|
jcenter() |
|
} |
|
|
|
dependencies { |
|
compile "io.vertx:vertx-core:$vertx_version" |
|
compile "io.vertx:vertx-web:$vertx_version" |
|
compile "io.vertx:vertx-web-client:$vertx_version" |
|
compile "io.vertx:vertx-lang-kotlin:$vertx_version" |
|
compile "io.vertx:vertx-lang-kotlin-coroutines:$vertx_version" |
|
|
|
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" |
|
|
|
compile "org.slf4j:slf4j-jdk14:$slf4j_version" |
|
|
|
compile "org.hsqldb:hsqldb:$hsqldb_version" |
|
|
|
compile "io.reactiverse:reactive-pg-client:$reactivepgclient_version" |
|
} |
|
|
|
// Redeploy watcher. |
|
run { |
|
args = ['run', mainVerticleName, |
|
"--launcher-class=$mainClassName", |
|
"--redeploy=src/**/*.*", |
|
"--on-redeploy=./gradlew classes" |
|
] |
|
} |
|
|
|
// Naming and packaging settings for the "shadow jar". |
|
shadowJar { |
|
baseName = 'app' |
|
classifier = 'shadow' |
|
|
|
manifest { |
|
attributes 'Main-Verticle': mainVerticleName |
|
} |
|
mergeServiceFiles { |
|
include 'META-INF/services/io.vertx.core.spi.VerticleFactory' |
|
} |
|
} |
|
|
|
task wrapper(type: Wrapper) { |
|
gradleVersion = '3.3' |
|
} |
|
|
|
// Heroku relies on the 'stage' task to deploy. |
|
task stage { |
|
dependsOn shadowJar |
|
} |
|
|
|
*/ |
|
|
|
// REPO: [email protected]:abner/vertx-kotlin-coroutines-gradle-template.git |
|
class MainVerticle : CoroutineVerticle() { |
|
private lateinit var client: PgPool; |
|
|
|
// TODO: Implement calls to webclient through a event bus |
|
// TODO: Implement service layer using coroutines with transaction handling |
|
// TODO: Implement config loading to use as base for Verticles |
|
|
|
suspend override fun start() { |
|
|
|
|
|
// Pool options |
|
val options = PgPoolOptions() |
|
.setPort(5432) |
|
.setHost("the-host") |
|
.setDatabase("the-db") |
|
.setUser("user") |
|
.setPassword("secret") |
|
.setMaxSize(5) |
|
|
|
client = PgClient.pool(vertx, options) |
|
|
|
// Populate database |
|
val statements = listOf( |
|
"CREATE TABLE MOVIE (ID VARCHAR(16) PRIMARY KEY, TITLE VARCHAR(256) NOT NULL)", |
|
"CREATE TABLE RATING (ID INTEGER IDENTITY PRIMARY KEY, value INTEGER, MOVIE_ID VARCHAR(16))", |
|
"INSERT INTO MOVIE (ID, TITLE) VALUES 'starwars', 'Star Wars'", |
|
"INSERT INTO MOVIE (ID, TITLE) VALUES 'indianajones', 'Indiana Jones'", |
|
"INSERT INTO MOVIE (ID, TITLE) VALUES 'meufilme', 'Meu filme'", |
|
"INSERT INTO RATING (VALUE, MOVIE_ID) VALUES 1, 'starwars'", |
|
"INSERT INTO RATING (VALUE, MOVIE_ID) VALUES 5, 'starwars'", |
|
"INSERT INTO RATING (VALUE, MOVIE_ID) VALUES 9, 'starwars'", |
|
"INSERT INTO RATING (VALUE, MOVIE_ID) VALUES 10, 'starwars'", |
|
"INSERT INTO RATING (VALUE, MOVIE_ID) VALUES 4, 'indianajones'", |
|
"INSERT INTO RATING (VALUE, MOVIE_ID) VALUES 7, 'indianajones'", |
|
"INSERT INTO RATING (VALUE, MOVIE_ID) VALUES 3, 'indianajones'", |
|
"INSERT INTO RATING (VALUE, MOVIE_ID) VALUES 9, 'indianajones'" |
|
) |
|
val connection = awaitResult<PgConnection> { client.getConnection(it) } |
|
connection.begin() { |
|
for (statement in statements) { |
|
awaitResult<Void> { connection.query(statement, it) } |
|
} |
|
} |
|
|
|
// Build Vert.x Web router |
|
val router = Router.router(vertx) |
|
router.get("/movie/:id").coroutineHandler { ctx -> getMovie(ctx) } |
|
router.post("/rateMovie/:id").coroutineHandler { ctx -> rateMovie(ctx) } |
|
router.get("/getRating/:id").coroutineHandler { ctx -> getRating(ctx) } |
|
|
|
// Start the server |
|
awaitResult<HttpServer> { vertx.createHttpServer() |
|
.requestHandler(router::accept) |
|
.listen(config.getInteger("http.port", 8080), it) |
|
} |
|
} |
|
|
|
// Send info about a movie |
|
suspend fun getMovie(ctx: RoutingContext) { |
|
val id = ctx.pathParam("id") |
|
val result = awaitResult<PgRowSet> { client.preparedQuery("SELECT TITLE FROM MOVIE WHERE ID=?", Tuple.of(id), it) } |
|
if (result.rowCount() === 1) { |
|
val title = result.iterator().next().getString("TITLE"); |
|
ctx.response().end(json { |
|
obj("id" to id, "title" to title).encode() |
|
}) |
|
} else { |
|
ctx.response().setStatusCode(404).end() |
|
} |
|
} |
|
|
|
// // Rate a movie |
|
// suspend fun rateMovie(ctx: RoutingContext) { |
|
// val movie = ctx.pathParam("id") |
|
// val rating = Integer.parseInt(ctx.queryParam("getRating")[0]) |
|
// val connection = awaitResult<SQLConnection> { client.getConnection(it) } |
|
// connection.use { |
|
// val result = awaitResult<ResultSet> { connection.queryWithParams("SELECT TITLE FROM MOVIE WHERE ID=?", json { array(movie) }, it) } |
|
// if (result.rows.size == 1) { |
|
// awaitResult<UpdateResult> { connection.updateWithParams("INSERT INTO RATING (VALUE, MOVIE_ID) VALUES ?, ?", json { array(rating, movie) }, it) } |
|
// ctx.response().setStatusCode(200).end() |
|
// } else { |
|
// ctx.response().setStatusCode(404).end() |
|
// } |
|
// } |
|
// } |
|
|
|
// // Get the current rating of a movie |
|
// suspend fun getRating(ctx: RoutingContext) { |
|
// val id = ctx.pathParam("id") |
|
// val result = awaitResult<ResultSet> { client.queryWithParams("SELECT AVG(VALUE) AS VALUE FROM RATING WHERE MOVIE_ID=?", json { array(id) }, it) } |
|
// ctx.response().end(json { |
|
// obj("id" to id, "getRating" to result.rows[0]["VALUE"]).encode() |
|
// }) |
|
// } |
|
} |
|
|
|
/** |
|
* An extension method for simplifying coroutines usage with Vert.x Web routers |
|
*/ |
|
fun Route.coroutineHandler(fn : suspend (RoutingContext) -> Unit) { |
|
handler { ctx -> |
|
launch(ctx.vertx().dispatcher()) { |
|
try { |
|
fn(ctx) |
|
} catch(e: Exception) { |
|
ctx.fail(e) |
|
} |
|
} |
|
} |
|
} |