DATACMNS-1508 - Polishing.

Add findAllById, deleteAll to CoroutineCrudRepository.

Keep CoroutineCrudRepository somewhat in sync with ReactiveCrudRepository by adding based methods for those accecpting Publisher.
Also add some test.

Update License Headers of Kotlin files and fix some warnings. Switch to kotlin-test-junit5.

Original pull request: #415.
This commit is contained in:
Christoph Strobl
2020-01-13 14:01:58 +01:00
committed by Mark Paluch
parent 93c708379e
commit 769b0f6af5
24 changed files with 171 additions and 48 deletions

View File

@@ -287,7 +287,8 @@
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-test</artifactId>
<artifactId>kotlin-test-junit5</artifactId>
<version>${kotlin}</version>
<scope>test</scope>
</dependency>

View File

@@ -40,13 +40,12 @@ import org.springframework.lang.Nullable;
* errors.
*
* @author Mark Paluch
* @author Christoph Strobl
* @since 2.3
* @see org.springframework.core.KotlinDetector#isKotlinReflectPresent()
*/
public final class KotlinReflectionUtils {
private static final int KOTLIN_KIND_CLASS = 1;
private KotlinReflectionUtils() {}
/**
@@ -64,7 +63,7 @@ public final class KotlinReflectionUtils {
return Arrays.stream(type.getDeclaredAnnotations()) //
.filter(annotation -> annotation.annotationType().getName().equals("kotlin.Metadata")) //
.map(annotation -> AnnotationUtils.getValue(annotation, "k")) //
.anyMatch(it -> Integer.valueOf(KOTLIN_KIND_CLASS).equals(it));
.anyMatch(it -> Integer.valueOf(KotlinClassHeaderKind.CLASS.id).equals(it));
}
/**
@@ -202,4 +201,15 @@ public final class KotlinReflectionUtils {
Method javaMethod = ReflectJvmMapping.getJavaMethod(function);
return javaMethod != null && javaMethod.equals(method);
}
private enum KotlinClassHeaderKind {
CLASS(1), FILE(2), SYNTHETIC_CLASS(3), MULTI_FILE_CLASS_FACADE(4), MULTI_FILE_CLASS_PART(5);
int id;
KotlinClassHeaderKind(int val) {
this.id = val;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2018-2019 the original author or authors.
* Copyright 2018-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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2019 the original author or authors.
* 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.
@@ -24,6 +24,7 @@ import reactor.core.publisher.Mono
* Interface for generic CRUD operations using Kotlin Coroutines on a repository for a specific type.
*
* @author Mark Paluch
* @author Christoph Strobl
* @since 2.3
* @see Flow
*/
@@ -96,6 +97,18 @@ interface CoroutineCrudRepository<T, ID> : Repository<T, ID> {
*/
fun findAllById(ids: Iterable<ID>): Flow<T>
/**
* Returns all instances of the type `T` with the given IDs.
* If some or all ids are not found, no entities are returned for these IDs.
* Note that the order of elements in the result is not guaranteed.
*
* @param ids must not be null nor contain any null values.
* @return [Flow] emitting the found entities. The size can be equal or less than the number of given
* ids.
* @throws IllegalArgumentException in case the given [ids][Iterable] or one of its items is null.
*/
fun findAllById(ids: Flow<ID>): Flow<T>
/**
* Returns the number of entities available.
*
@@ -128,6 +141,14 @@ interface CoroutineCrudRepository<T, ID> : Repository<T, ID> {
*/
suspend fun deleteAll(entities: Iterable<T>)
/**
* Deletes all given entities.
*
* @param entityStream must not be null.
* @throws IllegalArgumentException in case the given [entityStream][Flow] is null.
*/
fun <S : T> deleteAll(entityStream: Flow<S>)
/**
* Deletes all entities managed by the repository.
*/

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2019 the original author or authors.
* 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.

View File

@@ -112,7 +112,7 @@ public class DummyRepositoryFactory extends RepositoryFactorySupport {
/**
* @author Mark Paluch
*/
public static interface MyRepositoryQuery extends RepositoryQuery {
public interface MyRepositoryQuery extends RepositoryQuery {
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2017-2019 the original author or authors.
* Copyright 2017-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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2017-2019 the original author or authors.
* Copyright 2017-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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2017-2019 the original author or authors.
* Copyright 2017-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.
@@ -18,5 +18,4 @@ package org.springframework.data.mapping.context
/**
* @author Mark Paluch
*/
data class SimpleDataClass(val firstname: String, val lastname: String) {
}
data class SimpleDataClass(val firstname: String, val lastname: String)

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2017-2019 the original author or authors.
* Copyright 2017-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.
@@ -18,8 +18,7 @@ package org.springframework.data.mapping.context
/**
* @author Mark Paluch
*/
class TypeCreatingSyntheticClass {
}
class TypeCreatingSyntheticClass
fun foobar(args: Array<String>) {
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2018-2019 the original author or authors.
* Copyright 2018-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.
@@ -20,8 +20,7 @@ import java.util.*
/**
* @author Mark Paluch
*/
data class DataClassKt(val id: String) {
}
data class DataClassKt(val id: String)
data class ExtendedDataClassKt(val id: Long, val name: String) {
fun copy(name: String, id: Long): ExtendedDataClassKt {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2017-2019 the original author or authors.
* Copyright 2017-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.
@@ -109,8 +109,7 @@ class PreferredConstructorDiscovererUnitTests {
constructor(firstname: String, lastname: String) : this(firstname)
}
class DefaultConstructor(val firstname: String = "foo") {
}
class DefaultConstructor(val firstname: String = "foo")
class TwoDefaultConstructorsAnnotated(val firstname: String = "foo", val lastname: String = "bar") {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2017-2019 the original author or authors.
* Copyright 2017-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.
@@ -18,8 +18,7 @@ package org.springframework.data.mapping.model
/**
* @author Mark Paluch
*/
class TypeCreatingSyntheticClass {
}
class TypeCreatingSyntheticClass
fun foobar(args: Array<String>) {
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2018-2019 the original author or authors.
* Copyright 2018-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.
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mapping.model;
package org.springframework.data.mapping.model
import java.sql.Timestamp

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2018-2019 the original author or authors.
* Copyright 2018-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.
@@ -18,5 +18,4 @@ package org.springframework.data.mapping.model
/**
* @author Mark Paluch
*/
class ValueClassKt(val immutable: String) {
}
class ValueClassKt(val immutable: String)

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2018-2019 the original author or authors.
* Copyright 2018-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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2018-2019 the original author or authors.
* Copyright 2018-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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2008-2019 the original author or authors.
* Copyright 2008-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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2017-2019 the original author or authors.
* Copyright 2017-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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2019 the original author or authors.
* 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.
@@ -35,8 +35,8 @@ import org.springframework.data.repository.sample.User
class CoroutineCrudRepositoryCustomImplementationUnitTests {
val backingRepository = mockk<ReactiveCrudRepository<User, String>>()
lateinit var factory: DummyReactiveRepositoryFactory;
lateinit var coRepository: MyCoRepository;
lateinit var factory: DummyReactiveRepositoryFactory
lateinit var coRepository: MyCoRepository
@Before
fun before() {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2019 the original author or authors.
* 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.
@@ -17,13 +17,16 @@ package org.springframework.data.repository.kotlin
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.runBlocking
import org.assertj.core.api.Assertions.assertThat
import org.junit.Before
import org.junit.Test
import org.mockito.Mockito
import org.reactivestreams.Publisher
import org.springframework.data.repository.core.support.DummyReactiveRepositoryFactory
import org.springframework.data.repository.reactive.ReactiveCrudRepository
import org.springframework.data.repository.sample.User
@@ -40,8 +43,8 @@ import rx.Single
class CoroutineCrudRepositoryUnitTests {
val backingRepository = mockk<ReactiveCrudRepository<User, String>>()
lateinit var factory: DummyReactiveRepositoryFactory;
lateinit var coRepository: MyCoRepository;
lateinit var factory: DummyReactiveRepositoryFactory
lateinit var coRepository: MyCoRepository
@Before
fun before() {
@@ -54,7 +57,7 @@ class CoroutineCrudRepositoryUnitTests {
val sample = User()
every { backingRepository.findAll() }.returns(Flux.just(sample))
every { backingRepository.findAll() } returns Flux.just(sample)
val result = runBlocking {
coRepository.findAll().toList()
@@ -63,12 +66,54 @@ class CoroutineCrudRepositoryUnitTests {
assertThat(result).hasSize(1).containsOnly(sample)
}
@Test // DATACMNS-1508
fun shouldInvokeFindAllById() {
every { backingRepository.findAllById(any<Publisher<String>>()) } returns Flux.fromArray(arrayOf(User(), User()))
val result = runBlocking {
coRepository.findAllById(flowOf("user-1", "user-2")).toList()
}
assertThat(result).hasSize(2)
}
@Test // DATACMNS-1508
fun shouldInvokeSaveAllWhenGivenIterable() {
val sample = listOf(User(), User())
every { backingRepository.saveAll(any<Iterable<User>>()) } returns Flux.fromIterable(sample)
val result = runBlocking {
coRepository.saveAll(sample).toList()
}
assertThat(result).containsExactlyElementsOf(sample)
}
@Test // DATACMNS-1508
fun shouldInvokeSaveAllWhenGivenFlow() {
val u1 = User()
val u2 = User()
val sample = flowOf(u1, u2)
every { backingRepository.saveAll(any<Publisher<User>>()) } returns Flux.fromArray(arrayOf(u1, u2))
val result = runBlocking {
coRepository.saveAll(sample).toList()
}
assertThat(result).containsExactly(u1, u2)
}
@Test // DATACMNS-1508
fun shouldInvokeFindById() {
val sample = User()
every { backingRepository.findById("foo") }.returns(Mono.just(sample))
every { backingRepository.findById("foo") } returns Mono.just(sample)
val result = runBlocking {
coRepository.findById("foo")
@@ -77,6 +122,60 @@ class CoroutineCrudRepositoryUnitTests {
assertThat(result).isNotNull().isEqualTo(sample)
}
@Test // DATACMNS-1508
fun shouldInvokeExistsById() {
every { backingRepository.existsById("foo") } returns Mono.just(true)
val result = runBlocking {
coRepository.existsById("foo")
}
assertThat(result).isTrue()
}
@Test // DATACMNS-1508
fun shouldInvokeDeleteAll() {
every { backingRepository.deleteAll() } returns Mono.empty()
runBlocking {
coRepository.deleteAll()
}
verify { backingRepository.deleteAll() }
}
@Test // DATACMNS-1508
fun shouldInvokeDeleteAllWhenGivenIterable() {
val sample = listOf(User(), User())
every { backingRepository.deleteAll(any<Iterable<User>>()) } returns Mono.empty()
runBlocking {
coRepository.deleteAll(sample)
}
verify { backingRepository.deleteAll(sample) }
}
@Test // DATACMNS-1508
fun shouldInvokeDeleteAllWhenGivenFlow() {
val u1 = User()
val u2 = User()
val sample = flowOf(u1, u2)
every { backingRepository.deleteAll(any<Publisher<User>>()) } returns Mono.empty()
runBlocking {
coRepository.deleteAll(sample)
}
verify { backingRepository.deleteAll(any<Publisher<User>>()) }
}
@Test // DATACMNS-1508
fun shouldBridgeQueryMethod() {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2019 the original author or authors.
* 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.
@@ -19,7 +19,6 @@ import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import org.springframework.core.DefaultParameterNameDiscoverer
import org.springframework.core.MethodParameter
import org.springframework.core.ParameterNameDiscoverer
import kotlin.reflect.jvm.javaMethod
/**

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2017-2019 the original author or authors.
* Copyright 2017-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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2017-2019 the original author or authors.
* Copyright 2017-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.
@@ -18,8 +18,7 @@ package org.springframework.data.util
/**
* @author Mark Paluch
*/
class TypeCreatingSyntheticClass {
}
class TypeCreatingSyntheticClass
fun foobar(args: Array<String>) {
}