#1213 - Add support for Kotlin co-routines.

The ReactiveRepresentationModelAssembler and SimpleRepresentationModelAssembler both accept Flux inputs for entities. This change adds support for Kotlin co-routines to the same inputs.

Related pull request: #1258.
This commit is contained in:
Greg Turnquist
2020-04-03 15:35:18 -05:00
parent 1f8c0a4230
commit b46082b1b0
5 changed files with 221 additions and 2 deletions

29
pom.xml
View File

@@ -85,6 +85,8 @@
<spring.version>5.2.5.RELEASE</spring.version>
<spring-plugin.version>2.0.0.RELEASE</spring-plugin.version>
<kotlin.version>1.3.71</kotlin.version>
<kotlinx-coroutines.version>1.3.5</kotlinx-coroutines.version>
<mockk.version>1.9.3</mockk.version>
</properties>
<profiles>
@@ -704,6 +706,20 @@
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-bom</artifactId>
<version>${kotlin.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines-bom</artifactId>
<version>${kotlinx-coroutines.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
@@ -713,13 +729,16 @@
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<version>${kotlin.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-reflect</artifactId>
<version>${kotlin.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines-reactor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
@@ -728,6 +747,12 @@
<version>${kotlin.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.mockk</groupId>
<artifactId>mockk</artifactId>
<version>${mockk.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>

View File

@@ -0,0 +1,35 @@
/*
* Copyright 2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.hateoas.server.reactive
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.reactive.awaitFirst
import kotlinx.coroutines.reactor.asFlux
import org.springframework.hateoas.CollectionModel
import org.springframework.hateoas.RepresentationModel
import org.springframework.web.server.ServerWebExchange
/**
* Add support for Kotlin co-routines.
*
* @author Juergen Zimmerman
* @author Greg Turnquist
* @since 1.1
*/
suspend fun <T : Any, D : RepresentationModel<D>> ReactiveRepresentationModelAssembler<T, D>.toCollectionModel(
entities: Flow<T>,
exchange: ServerWebExchange): CollectionModel<D> = toCollectionModel(entities.asFlux(), exchange).awaitFirst()

View File

@@ -0,0 +1,35 @@
/*
* Copyright 2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.hateoas.server.reactive
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.reactive.awaitFirst
import kotlinx.coroutines.reactor.asFlux
import org.springframework.hateoas.CollectionModel
import org.springframework.hateoas.EntityModel
import org.springframework.web.server.ServerWebExchange
/**
* Add support for Kotlin co-routines.
*
* @author Juergen Zimmerman
* @author Greg Turnquist
* @since 1.1
*/
suspend fun <T : Any> SimpleReactiveRepresentationModelAssembler<T>.toCollectionModel(
entities: Flow<T>,
exchange: ServerWebExchange): CollectionModel<EntityModel<T>> = toCollectionModel(entities.asFlux(), exchange).awaitFirst()

View File

@@ -0,0 +1,65 @@
/*
* Copyright 2019-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.hateoas.server.reactive
import io.mockk.mockk
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.runBlocking
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.springframework.hateoas.EntityModel
import org.springframework.hateoas.Link
import org.springframework.hateoas.LinkRelation
import org.springframework.hateoas.TestUtils
import org.springframework.web.server.ServerWebExchange
import reactor.core.publisher.Mono
/**
* @author Greg Turnquist
* @since 1.1
*/
class ReactiveRepresentationModelAssemblerBuilderDslUnitTest : TestUtils() {
private val EMPLOYEES_RELATION = LinkRelation.of("employees")
private val employees = flow {
emit(Employee("Frodo"))
emit(Employee("Bilbo"))
}
@Test // #1213
fun `Kotlin Flows should render as CollectionModels`() {
val testResourceAssembler = TestResourceAssembler()
val exchange = mockk<ServerWebExchange>()
runBlocking {
val collectionModel = testResourceAssembler.toCollectionModel(employees, exchange)
assertThat(collectionModel.content.flatMap { entityModel -> entityModel.links })
.containsExactlyInAnyOrder(Link.of("/employees", EMPLOYEES_RELATION), Link.of("/employees", EMPLOYEES_RELATION))
}
}
class TestResourceAssembler : ReactiveRepresentationModelAssembler<Employee, EntityModel<Employee>> {
override fun toModel(entity: Employee, exchange: ServerWebExchange): Mono<EntityModel<Employee>> {
return Mono.just(EntityModel.of(entity, Link.of("/employees", LinkRelation.of("employees"))))
}
}
data class Employee(val name: String)
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2019-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.hateoas.server.reactive
import io.mockk.mockk
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.runBlocking
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.springframework.hateoas.*
import org.springframework.web.server.ServerWebExchange
/**
* @author Greg Turnquist
* @since 1.1
*/
class SimpleReactiveRexpresentationModelAssemblerBuilderDslUnitTest : TestUtils() {
private val employees = flow {
emit(Employee("Frodo"))
emit(Employee("Bilbo"))
}
@Test // #1213
fun `Kotlin Flows should render as CollectionModels`() {
val testResourceAssembler = SimpleTestResourceAssembler()
val exchange = mockk<ServerWebExchange>()
runBlocking {
val collectionModel = testResourceAssembler.toCollectionModel(employees, exchange)
assertThat(collectionModel.links).containsExactly(Link.of("/employees"))
}
}
class SimpleTestResourceAssembler : SimpleReactiveRepresentationModelAssembler<Employee> {
override fun addLinks(resources: CollectionModel<EntityModel<Employee>>, exchange: ServerWebExchange): CollectionModel<EntityModel<Employee>> {
resources.add(Link.of("/employees"))
return resources
}
}
data class Employee(val name: String)
}