This module serves two purposes:
- It makes it easy to consume REST endpoints exported by another server and process/show them in your VoK server.
- It makes it easy to test your VoK REST server endpoints.
We're using the built-in HttpClient.
Note: If you wish to expose your objects from your app, rather than consume objects from some other service, please see the vok-rest module.
Include dependency on this module to your app; just add the following Gradle dependency to your build.gradle
:
dependencies {
compile("eu.vaadinonkotlin:vok-rest-client:x.y.z")
}
Note: to obtain the newest version see above for the most recent tag
You simply use the HttpClient
to make HTTP calls. The client is built-in in Java 9 and higher.
See HttpClient for the documentation on the API.
VoK introduces the exec
method which helps tremendously with synchronous calls. It fails automatically when the response is not in 200..299.
See the example code for more details:
// Demoes direct access via httpclient
class PersonRestClient(val baseUrl: String) {
init {
require(!baseUrl.endsWith("/")) { "$baseUrl must not end with a slash" }
}
private val client: HttpClient = HttpClientVokPlugin.httpClient!!
fun helloWorld(): String {
val request = "$baseUrl/helloworld".buildUrl().buildRequest()
return client.exec(request) { response -> response.bodyAsString() }
}
fun getAll(): List<Person> {
val request = baseUrl.buildUrl().buildRequest()
return client.exec(request) { response -> response.jsonArray(Person::class.java) }
}
}
val client = PersonRestClient("http://localhost:8080/rest/person")
println(client.getAll())
The HttpClientVokPlugin.httpClient
is constructed automatically by the
VOK module loading mechanism in HttpClientVokPlugin.init()
. Alternatively, if you
need to customize/configure the HttpClient
instance, you can simply assign your
own HttpClient
instance to HttpClientVokPlugin.httpClient
before
the VoK is initialized. Your instance will not be overwritten by init()
.
Just use the buildUrl
extension method which uses URIBuilder under the belt:
val request = "http://localhost:8080/rest/person".buildUrl {
addParameter("q", "foo bar")
} .buildRequest()
client.exec(request) { ... }
You can use the CrudClient
class to access CRUD REST endpoints:
val crud = CrudClient("http://localhost:8080/rest/person/", Person::class.java)
println(crud.getAll())
The CRUD client supports filtering, paging and sorting. It also implements the DataLoader
interface
which is very easy to turn into Vaadin's DataProvider
which can
then be fed directly to the Vaadin Grid
component, making it easy to display
CRUD contents in a tabular fashion:
val crud = CrudClient("http://localhost:8080/rest/person/", Person::class.java)
val dp = DataLoaderAdapter(Person::class.java, crud, { it.id!! }).withConfigurableFilter2()
grid.dataProvider = dp
Just add dependency on either vok-util-vaadin8 or
vok-util-vaadin10 to get access to the DataLoaderAdapter
class.
Please read more about this in the Accessing NoSQL or REST data sources.
The CRUD Client expects the CRUD endpoint to expose data in the following fashion:
GET /rest/users
returns all usersGET /rest/users?select=count
returns a single number - the count of all users. This is only necessary forgetCount()
or if you plan to use this client as a backend for Vaadin Grid.GET /rest/users/22
returns one usersPOST /rest/users
will create an userPATCH /rest/users/22
will update an userDELETE /rest/users/22
will delete an user
Paging/sorting/filtering is supported: the following query parameters will simply be added to the "get all" URL request:
limit
andoffset
for result paging. Both must be 0 or greater. The server may impose max value limit on thelimit
parameter.sort_by=-last_modified,+email,first_name
- a list of sorting clauses. The server may restrict sorting by only a selected subset of properties.- The filters are simply converted to query parameters, for example
age=81
.OpFilter
s are also supported: the value will be prefixed with a special operator prefix:eq:
,lt:
,lte:
,gt:
,gte:
,ilike:
,like:
,isnull:
,isnotnull:
, for exampleage=lt:25
. A full example isname=ilike:martin&age=lte:70&age=gte:20&birthdate=isnull:&grade=5
. OR filters are not supported - passingOrFilter
will causegetAll()
to throwIllegalArgumentException
.
All column names are expected to be Kotlin property name of the bean.
Often the endpoints are limited in how many items it can retrieve. For example if the CrudClient
is tied to Vaadin Grid,
the Grid may sometimes ask for as many as 200 items. If the endpoint is limited to return, say, 100 items tops, the
fetch will fail.
To limit the number of data fetched at once, you can simply wrap the loader in the ChunkFetchingLoader
as shown below:
val crud = CrudClient("http://localhost:8080/rest/person/", Person::class.java)
val limiter = ChunkFetchingLoader(crud, 100)
val dp = DataLoaderAdapter(Person::class.java, limiter, { it.id!! }).withConfigurableFilter2()
grid.dataProvider = dp
The limiter will make sure that at most 100 items will be fetched at once. If need be, the loader will
simply poll CrudClient
multiple times, downloading chunks of 100 items
until the necessary number of items has been fetched.
If you use vok-rest-client
from within of your VoK app then VoK will take care of properly
initializing and destroying of this module. However, if you plan to use this module for testing purposes, it's important to properly initialize it
and destroy it after all of your tests are done.
You need to do one of these:
- Call
VaadinOnKotlin.init()
before all tests andVaadinOnKotlin.destroy()
after all tests. That will also properly initialize and destroy thevok-rest-client
module. In the example below, this is done via the call tousingApp()
function, which in turn callsBootstrap().contextInitialized(null)
andBootstrap().contextDestroyed(null)
. - Or you need to init the module manually:
HttpClientVokPlugin().init()
andHttpClientVokPlugin().destroy()
Otherwise the HttpClient won't get initialized and the test will fail with NPE.
Example test:
class PersonRestTest : DynaTest({
lateinit var javalin: Javalin
beforeGroup {
javalin = Javalin.create().disableStartupBanner()
javalin.configureRest().start(9876)
}
afterGroup { javalin.stop() }
usingApp() // to bootstrap the app to have access to the database.
lateinit var client: PersonRestClient
beforeEach { client = PersonRestClient("http://localhost:9876/rest/") }
test("hello world") {
expect("Hello World") { client.helloWorld() }
}
test("get all users") {
expectList() { client.getAll() }
val p = Person(name = "Duke Leto Atreides", age = 45, dateOfBirth = LocalDate.of(1980, 5, 1), maritalStatus = MaritalStatus.Single, alive = false)
p.save()
val all = client.getAll()
p.created = all[0].created
expectList(p) { all }
}
})
In order to start Javalin with Jetty, you also need to add Jetty to your test classpath:
dependencies {
testCompile("org.eclipse.jetty.websocket:websocket-server:9.4.44.v20210927")
}
Gson by default only export non-transient fields. It only exports actual Java fields, or only Kotlin properties that are backed by actual fields;
it ignores computed Kotlin properties such as val reviews: List<Review> get() = Review.findAll()
.
To reconfigure Gson, just set a new instance to HttpClientVokPlugin.gson
. To reconfigure HttpClient
just set HttpClientVokPlugin.httpClient
before calling VaadinOnKotlin.init()
.
Please see Gson User Guide for more details.