Browse Source

Provide RepositoryMethodMetadata during method invocation.

Provide context during repository method invocation to potential consumers.

See: #3090
Original Pull Request: #3093
pull/3136/head
Christoph Strobl 1 year ago
parent
commit
647d9fd949
No known key found for this signature in database
GPG Key ID: E6054036D0C37A4B
  1. 91
      src/main/antora/modules/ROOT/pages/repositories/custom-implementations.adoc
  2. 79
      src/main/java/org/springframework/data/repository/core/support/DefaultRepositoryMethodMetadata.java
  3. 5
      src/main/java/org/springframework/data/repository/core/support/QueryExecutorMethodInterceptor.java
  4. 19
      src/main/java/org/springframework/data/repository/core/support/RepositoryComposition.java
  5. 39
      src/main/java/org/springframework/data/repository/core/support/RepositoryMethodInvoker.java
  6. 44
      src/main/java/org/springframework/data/repository/core/support/RepositoryMethodMetadata.java
  7. 65
      src/test/java/org/springframework/data/repository/core/support/RepositoryCompositionUnitTests.java
  8. 29
      src/test/java/org/springframework/data/repository/core/support/RepositoryMethodInvokerUnitTests.java

91
src/main/antora/modules/ROOT/pages/repositories/custom-implementations.adoc

@ -238,6 +238,97 @@ XML:: @@ -238,6 +238,97 @@ XML::
======
====
[[repositories.spring-factories]]
==== Registering Fragments with spring.factories
As already mentioned in the <<repositories.configuration>> section, the infrastructure only auto detects fragments within the repositories base package. Therefore fragments residing in another location or maybe contributed by an external archive will not be found if they do not share a common namespace.
Registering fragments within `spring.factories` allows you to circumvent this restriction as explained in the following section.
Imagine you'd like to provide some custom search functionality usable across multiple repositories for your organization leveraging a text search index.
First all you need is the fragment interface. Please note the generic `<T>` parameter to align the fragment with the repository domain type.
====
[source,java]
----
package com.acme.search;
public interface SearchExtension<T> {
List<T> search(String text, Limit limit);
}
----
====
Let's assume the actual full text search is available via a `SearchService` that is registered as a `Bean` within the context so we can consume it in our `SearchExtension` implementation. All we need to run the search is the collection/index name and a object mapper that converts the search results into actual domain objects as sketched out below.
====
[source,java]
----
package com.acme.search;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Limit;
import org.springframework.data.repository.core.support.RepositoryMethodMetadata;
class DefaultSearchExtension<T> implements SearchExtension<T> {
private SearchService service;
DefaultSearchExtension(@Autowired SearchService service) {
this.service = service;
}
public List<T> search(String text, Limit limit) {
return search(RepositoryMethodMetadata.get(), text, limit);
}
List<T> search(RepositoryMethodMetadata metadata, String text, Limit limit) {
Class<T> domainType = metadata.repository().getDomainType();
String indexName = domainType.getSimpleName().toLowerCase();
List<String> jsonResult = service.search(indexName, text, 0, limit.max());
return jsonResult.stream().map( ... ).collect(toList());
}
}
----
====
In the snipped above we use `RepositoryMethodMetadata.get()` to get hold of metadata for the actual method invocation. In doing so we can access additional information attached to the repository. In this case we use the repositories domain type to identify the name of the index to be searched.
[TIP]
====
For testing you can use `TransactionSynchronizationManager.bindResource(RepositoryMethodMetadata.class, metadata)` to provide repository method metadata.
====
Now that we've got both, the fragments declaration and implementation we can register it in the `META-INF/spring.factories` file, package things up if needed and we're good to go.
====
[source,properties]
----
com.acme.search.SearchExtension=com.acme.search.DefaultSearchExtension
----
====
To make use of the extension simply add the interface to the repository as shown below. The infrastructure will take care placing the required `RepositoryMethodMetadata` so all that
====
[source,java]
----
package io.my.movies;
import com.acme.search.SearchExtension;
import org.springframework.data.repository.CrudRepository;
public interface MovieRepository extends CrudRepository<Movie, String>, SearchExtension<Movie> {
}
----
====
[[repositories.customize-base-repository]]
== Customize the Base Repository

79
src/main/java/org/springframework/data/repository/core/support/DefaultRepositoryMethodMetadata.java

@ -0,0 +1,79 @@ @@ -0,0 +1,79 @@
/*
* Copyright 2024 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.data.repository.core.support;
import java.lang.reflect.Method;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.lang.Nullable;
import org.springframework.transaction.support.TransactionSynchronizationManager;
/**
* @author Christoph Strobl
*/
class DefaultRepositoryMethodMetadata implements RepositoryMethodMetadata {
private final RepositoryMetadata repositoryMetadata;
private final MethodMetadata methodMetadata;
DefaultRepositoryMethodMetadata(RepositoryMetadata repositoryMetadata, MethodMetadata methodMetadata) {
this.repositoryMetadata = repositoryMetadata;
this.methodMetadata = methodMetadata;
}
static DefaultRepositoryMethodMetadata repositoryMethodMetadata(RepositoryMetadata repositoryMetadata,
Method declaredMethod) {
return repositoryMethodMetadata(repositoryMetadata, declaredMethod, null);
}
static DefaultRepositoryMethodMetadata repositoryMethodMetadata(RepositoryMetadata repositoryMetadata,
Method declaredMethod, @Nullable Method targetMethod) {
return new DefaultRepositoryMethodMetadata(repositoryMetadata,
new DefaultMethodMetadata(declaredMethod, targetMethod));
}
static void bind(RepositoryMethodMetadata metadata) {
TransactionSynchronizationManager.bindResource(RepositoryMethodMetadata.class, metadata);
}
static void unbind() {
TransactionSynchronizationManager.unbindResourceIfPossible(RepositoryMethodMetadata.class);
}
@Override
public RepositoryMetadata repository() {
return repositoryMetadata;
}
@Override
public MethodMetadata method() {
return methodMetadata;
}
@Override
public String toString() {
return "DefaultRepositoryMethodMetadata{" + "repository=" + repositoryMetadata.getRepositoryInterface()
+ ", domainType=" + repositoryMetadata.getDomainType() + ", invokedMethod=" + methodMetadata.declaredMethod()
+ ", targetMethod=" + methodMetadata.targetMethod() + '}';
}
record DefaultMethodMetadata(Method declaredMethod, @Nullable Method targetMethod) implements MethodMetadata {
}
}

5
src/main/java/org/springframework/data/repository/core/support/QueryExecutorMethodInterceptor.java

@ -33,6 +33,7 @@ import org.springframework.data.repository.core.NamedQueries; @@ -33,6 +33,7 @@ import org.springframework.data.repository.core.NamedQueries;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.support.RepositoryInvocationMulticaster.DefaultRepositoryInvocationMulticaster;
import org.springframework.data.repository.core.support.RepositoryInvocationMulticaster.NoOpRepositoryInvocationMulticaster;
import org.springframework.data.repository.core.support.RepositoryMethodMetadata.MethodMetadata;
import org.springframework.data.repository.query.QueryCreationException;
import org.springframework.data.repository.query.QueryLookupStrategy;
import org.springframework.data.repository.query.QueryMethod;
@ -163,7 +164,9 @@ class QueryExecutorMethodInterceptor implements MethodInterceptor { @@ -163,7 +164,9 @@ class QueryExecutorMethodInterceptor implements MethodInterceptor {
RepositoryMethodInvoker invocationMetadata = invocationMetadataCache.get(method);
if (invocationMetadata == null) {
invocationMetadata = RepositoryMethodInvoker.forRepositoryQuery(method, queries.get(method));
DefaultRepositoryMethodMetadata repositoryMethodMetadata = DefaultRepositoryMethodMetadata.repositoryMethodMetadata(repositoryInformation, method);
invocationMetadata = RepositoryMethodInvoker.forRepositoryQuery(repositoryMethodMetadata, queries.get(method));
invocationMetadataCache.put(method, invocationMetadata);
}

19
src/main/java/org/springframework/data/repository/core/support/RepositoryComposition.java

@ -32,6 +32,7 @@ import java.util.stream.Stream; @@ -32,6 +32,7 @@ import java.util.stream.Stream;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.MethodLookup.InvokedMethod;
import org.springframework.data.repository.core.support.RepositoryInvocationMulticaster.NoOpRepositoryInvocationMulticaster;
import org.springframework.data.repository.core.support.RepositoryMethodMetadata.MethodMetadata;
import org.springframework.data.repository.util.ReactiveWrapperConverters;
import org.springframework.data.util.ReactiveWrappers;
import org.springframework.data.util.Streamable;
@ -281,7 +282,7 @@ public class RepositoryComposition { @@ -281,7 +282,7 @@ public class RepositoryComposition {
ReflectionUtils.makeAccessible(methodToCall);
return fragments.invoke(metadata != null ? metadata.getRepositoryInterface() : method.getDeclaringClass(), listener,
return fragments.invoke(metadata, listener,
method, methodToCall, argumentConverter.apply(methodToCall, args));
}
@ -369,7 +370,6 @@ public class RepositoryComposition { @@ -369,7 +370,6 @@ public class RepositoryComposition {
private final List<RepositoryFragment<?>> fragments;
private RepositoryFragments(List<RepositoryFragment<?>> fragments) {
this.fragments = fragments;
}
@ -382,6 +382,10 @@ public class RepositoryComposition { @@ -382,6 +382,10 @@ public class RepositoryComposition {
return EMPTY;
}
public static RepositoryFragments empty(RepositoryMetadata metadata) {
return EMPTY;
}
/**
* Create {@link RepositoryFragments} from just implementation objects.
*
@ -484,7 +488,7 @@ public class RepositoryComposition { @@ -484,7 +488,7 @@ public class RepositoryComposition {
/**
* Invoke {@link Method} by resolving the fragment that implements a suitable method.
*
* @param repositoryInterface
* @param metadata
* @param listener
* @param invokedMethod invoked method as per invocation on the interface.
* @param methodToCall backend method that is backing the call.
@ -493,7 +497,7 @@ public class RepositoryComposition { @@ -493,7 +497,7 @@ public class RepositoryComposition {
* @throws Throwable
*/
@Nullable
Object invoke(Class<?> repositoryInterface, RepositoryInvocationMulticaster listener, Method invokedMethod,
Object invoke(@Nullable RepositoryMetadata metadata, RepositoryInvocationMulticaster listener, Method invokedMethod,
Method methodToCall, Object[] args) throws Throwable {
RepositoryFragment<?> fragment = fragmentCache.computeIfAbsent(methodToCall, this::findImplementationFragment);
@ -507,12 +511,15 @@ public class RepositoryComposition { @@ -507,12 +511,15 @@ public class RepositoryComposition {
if (repositoryMethodInvoker == null) {
repositoryMethodInvoker = RepositoryMethodInvoker.forFragmentMethod(invokedMethod, optional.get(),
DefaultRepositoryMethodMetadata repositoryMethodMetadata = DefaultRepositoryMethodMetadata.repositoryMethodMetadata(metadata, invokedMethod, methodToCall);
repositoryMethodInvoker = RepositoryMethodInvoker.forFragmentMethod(repositoryMethodMetadata, optional.get(),
methodToCall);
invocationMetadataCache.put(invokedMethod, repositoryMethodInvoker);
}
return repositoryMethodInvoker.invoke(repositoryInterface, listener, args);
Class<?> target = (metadata != null && metadata.getRepositoryInterface() != null) ? metadata.getRepositoryInterface() : invokedMethod.getDeclaringClass();
return repositoryMethodInvoker.invoke(target, listener, args);
}
private RepositoryFragment<?> findImplementationFragment(Method key) {

39
src/main/java/org/springframework/data/repository/core/support/RepositoryMethodInvoker.java

@ -18,12 +18,19 @@ package org.springframework.data.repository.core.support; @@ -18,12 +18,19 @@ package org.springframework.data.repository.core.support;
import kotlin.Unit;
import kotlin.reflect.KFunction;
import kotlinx.coroutines.flow.Flow;
import org.springframework.core.type.MethodMetadata;
import org.springframework.data.repository.core.CrudMethods;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.util.TypeInformation;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Set;
import java.util.stream.Stream;
import org.reactivestreams.Publisher;
@ -46,8 +53,8 @@ import org.springframework.lang.Nullable; @@ -46,8 +53,8 @@ import org.springframework.lang.Nullable;
* @author Mark Paluch
* @author Christoph Strobl
* @since 2.4
* @see #forFragmentMethod(Method, Object, Method)
* @see #forRepositoryQuery(Method, RepositoryQuery)
// * @see #forFragmentMethod(Method, Object, Method)
// * @see #forRepositoryQuery(Method, RepositoryQuery)
* @see RepositoryQuery
* @see RepositoryComposition
*/
@ -57,11 +64,13 @@ abstract class RepositoryMethodInvoker { @@ -57,11 +64,13 @@ abstract class RepositoryMethodInvoker {
private final Class<?> returnedType;
private final Invokable invokable;
private final boolean suspendedDeclaredMethod;
protected RepositoryMethodMetadata repositoryMethodMetadata;
@SuppressWarnings("ReactiveStreamsUnusedPublisher")
protected RepositoryMethodInvoker(Method method, Invokable invokable) {
protected RepositoryMethodInvoker(RepositoryMethodMetadata repositoryMethodMetadata, Invokable invokable) {
this.method = method;
this.repositoryMethodMetadata = repositoryMethodMetadata;
this.method = repositoryMethodMetadata.method().declaredMethod();
if (KotlinDetector.isKotlinReflectPresent()) {
@ -116,7 +125,7 @@ abstract class RepositoryMethodInvoker { @@ -116,7 +125,7 @@ abstract class RepositoryMethodInvoker {
}
}
static RepositoryQueryMethodInvoker forRepositoryQuery(Method declaredMethod, RepositoryQuery query) {
static RepositoryQueryMethodInvoker forRepositoryQuery(RepositoryMethodMetadata declaredMethod, RepositoryQuery query) {
return new RepositoryQueryMethodInvoker(declaredMethod, query);
}
@ -128,7 +137,7 @@ abstract class RepositoryMethodInvoker { @@ -128,7 +137,7 @@ abstract class RepositoryMethodInvoker {
* @param baseMethod the base method to call on fragment {@code instance}.
* @return {@link RepositoryMethodInvoker} to call a fragment {@link Method}.
*/
static RepositoryMethodInvoker forFragmentMethod(Method declaredMethod, Object instance, Method baseMethod) {
static RepositoryMethodInvoker forFragmentMethod(RepositoryMethodMetadata declaredMethod, Object instance, Method baseMethod) {
return new RepositoryFragmentMethodInvoker(declaredMethod, instance, baseMethod);
}
@ -167,6 +176,10 @@ abstract class RepositoryMethodInvoker { @@ -167,6 +176,10 @@ abstract class RepositoryMethodInvoker {
try {
if(RepositoryMethodMetadata.get() == null && repositoryMethodMetadata != null) {
DefaultRepositoryMethodMetadata.bind(repositoryMethodMetadata);
}
Object result = invokable.invoke(args);
if (result != null && ReactiveWrappers.supports(result.getClass())) {
@ -184,6 +197,8 @@ abstract class RepositoryMethodInvoker { @@ -184,6 +197,8 @@ abstract class RepositoryMethodInvoker {
} catch (Exception e) {
multicaster.notifyListeners(method, args, computeInvocationResult(invocationResultCaptor.error(e)));
throw e;
} finally {
DefaultRepositoryMethodMetadata.unbind();
}
}
@ -202,7 +217,7 @@ abstract class RepositoryMethodInvoker { @@ -202,7 +217,7 @@ abstract class RepositoryMethodInvoker {
* Implementation to invoke query methods.
*/
private static class RepositoryQueryMethodInvoker extends RepositoryMethodInvoker {
public RepositoryQueryMethodInvoker(Method method, RepositoryQuery repositoryQuery) {
public RepositoryQueryMethodInvoker(RepositoryMethodMetadata method, RepositoryQuery repositoryQuery) {
super(method, repositoryQuery::execute);
}
}
@ -255,15 +270,19 @@ abstract class RepositoryMethodInvoker { @@ -255,15 +270,19 @@ abstract class RepositoryMethodInvoker {
*/
private static class RepositoryFragmentMethodInvoker extends RepositoryMethodInvoker {
public RepositoryFragmentMethodInvoker(Method declaredMethod, Object instance, Method baseClassMethod) {
this(CoroutineAdapterInformation.create(declaredMethod, baseClassMethod), declaredMethod, instance,
public RepositoryFragmentMethodInvoker(RepositoryMethodMetadata metadata, Object instance, Method baseClassMethod) {
this(CoroutineAdapterInformation.create(metadata.method().declaredMethod(), baseClassMethod), metadata, instance,
baseClassMethod);
}
public RepositoryFragmentMethodInvoker(CoroutineAdapterInformation adapterInformation, Method declaredMethod,
public RepositoryFragmentMethodInvoker(CoroutineAdapterInformation adapterInformation, RepositoryMethodMetadata declaredMethod,
Object instance, Method baseClassMethod) {
super(declaredMethod, args -> {
try {
if (adapterInformation.shouldAdaptReactiveToSuspended()) {
/*
* Kotlin suspended functions are invoked with a synthetic Continuation parameter that keeps track of the Coroutine context.

44
src/main/java/org/springframework/data/repository/core/support/RepositoryMethodMetadata.java

@ -0,0 +1,44 @@ @@ -0,0 +1,44 @@
/*
* Copyright 2024 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.data.repository.core.support;
import java.lang.reflect.Method;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.lang.Nullable;
import org.springframework.transaction.support.TransactionSynchronizationManager;
/**
* @author Christoph Strobl
*/
public interface RepositoryMethodMetadata {
@Nullable
static RepositoryMethodMetadata get() {
return (RepositoryMethodMetadata) TransactionSynchronizationManager.getResource(RepositoryMethodMetadata.class);
}
MethodMetadata method();
RepositoryMetadata repository();
interface MethodMetadata {
Method declaredMethod();
@Nullable Method targetMethod();
}
}

65
src/test/java/org/springframework/data/repository/core/support/RepositoryCompositionUnitTests.java

@ -15,8 +15,12 @@ @@ -15,8 +15,12 @@
*/
package org.springframework.data.repository.core.support;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.Mockito.verify;
import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@ -27,8 +31,11 @@ import org.springframework.data.annotation.Id; @@ -27,8 +31,11 @@ import org.springframework.data.annotation.Id;
import org.springframework.data.domain.Example;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.RepositoryComposition.RepositoryFragments;
import org.springframework.data.repository.query.QueryByExampleExecutor;
import org.springframework.lang.Nullable;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils;
/**
@ -52,8 +59,7 @@ class RepositoryCompositionUnitTests { @@ -52,8 +59,7 @@ class RepositoryCompositionUnitTests {
RepositoryInformation repositoryInformation = new DefaultRepositoryInformation(
new DefaultRepositoryMetadata(PersonRepository.class), backingRepo.getClass(), RepositoryComposition.empty());
var mixin = RepositoryFragment.implemented(QueryByExampleExecutor.class,
queryByExampleExecutor);
var mixin = RepositoryFragment.implemented(QueryByExampleExecutor.class, queryByExampleExecutor);
var base = RepositoryFragment.implemented(backingRepo);
@ -139,8 +145,7 @@ class RepositoryCompositionUnitTests { @@ -139,8 +145,7 @@ class RepositoryCompositionUnitTests {
assertThatExceptionOfType(FragmentNotImplementedException.class) //
.isThrownBy(mixed::validateImplementation) //
.withMessageContaining(
"Fragment org.springframework.data.repository.query.QueryByExampleExecutor")
.withMessageContaining("Fragment org.springframework.data.repository.query.QueryByExampleExecutor")
.withMessageContaining("has no implementation");
}
@ -165,6 +170,33 @@ class RepositoryCompositionUnitTests { @@ -165,6 +170,33 @@ class RepositoryCompositionUnitTests {
.containsSequence(initial, structural);
}
@Test // GH-3090
void fragmentInvocationProvidesRepositoryMethodMetadata() throws Throwable {
RepositoryInformation repositoryInformation = new DefaultRepositoryInformation(
new DefaultRepositoryMetadata(CapturingRepository.class), CapturingRepository.class,
RepositoryComposition.empty());
MethodMetadataCapturingMixin capturingMixin = new MethodMetadataCapturingMixin();
RepositoryFragment<?> foo = RepositoryFragment.implemented(capturingMixin);
var fooBar = RepositoryComposition.of(RepositoryFragments.of(foo))
.withMethodLookup(MethodLookups.forRepositoryTypes(repositoryInformation)).withMetadata(repositoryInformation);
var getString = ReflectionUtils.findMethod(CapturingRepository.class, "getString");
assertThat(getString).isNotNull();
fooBar.invoke(fooBar.findMethod(getString).get());
RepositoryMethodMetadata lastValue = capturingMixin.getLastValue();
assertThat(lastValue.repository()).isNotNull().extracting(RepositoryMetadata::getRepositoryInterface)
.isEqualTo(CapturingRepository.class);
// TODO: I'm actually lost on that one
// assertThat(lastValue.method()).isNotNull().extracting(MethodMetadata::declaredMethod).isEqualTo(getString);
// assertThat(lastValue.method()).isNotNull().extracting(MethodMetadata::targetMethod).isEqualTo(FooMixin.class.getMethod("getString"));
}
interface PersonRepository extends Repository<Person, String>, QueryByExampleExecutor<Person> {
Person save(Person entity);
@ -204,6 +236,10 @@ class RepositoryCompositionUnitTests { @@ -204,6 +236,10 @@ class RepositoryCompositionUnitTests {
}
interface CapturingRepository extends Repository<Person, String>, FooMixin {
}
interface FooMixin {
String getString();
@ -218,6 +254,23 @@ class RepositoryCompositionUnitTests { @@ -218,6 +254,23 @@ class RepositoryCompositionUnitTests {
}
}
class MethodMetadataCapturingMixin implements FooMixin {
List<RepositoryMethodMetadata> captured = new ArrayList<>(3);
@Override
public String getString() {
captured.add(RepositoryMethodMetadata.get());
return FooMixinImpl.INSTANCE.getString();
}
@Nullable
RepositoryMethodMetadata getLastValue() {
return CollectionUtils.lastElement(captured);
}
}
interface BarMixin {
String getString();

29
src/test/java/org/springframework/data/repository/core/support/RepositoryMethodInvokerUnitTests.java

@ -15,6 +15,18 @@ @@ -15,6 +15,18 @@
*/
package org.springframework.data.repository.core.support;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import kotlin.coroutines.Continuation;
import kotlinx.coroutines.reactive.ReactiveFlowKt;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Iterator;
@ -26,8 +38,6 @@ import java.util.concurrent.TimeUnit; @@ -26,8 +38,6 @@ import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.stream.Stream;
import kotlin.coroutines.Continuation;
import kotlinx.coroutines.reactive.ReactiveFlowKt;
import org.assertj.core.api.Assertions;
import org.assertj.core.data.Percentage;
import org.jetbrains.annotations.NotNull;
@ -39,26 +49,18 @@ import org.mockito.internal.stubbing.answers.AnswersWithDelay; @@ -39,26 +49,18 @@ import org.mockito.internal.stubbing.answers.AnswersWithDelay;
import org.mockito.internal.stubbing.answers.Returns;
import org.mockito.junit.jupiter.MockitoExtension;
import org.reactivestreams.Subscription;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.CoroutineRepositoryMetadataUnitTests.MyCoroutineRepository;
import org.springframework.data.repository.core.support.RepositoryMethodInvocationListener.RepositoryMethodInvocation;
import org.springframework.data.repository.core.support.RepositoryMethodInvocationListener.RepositoryMethodInvocationResult.State;
import org.springframework.data.repository.core.support.RepositoryMethodMetadata.MethodMetadata;
import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
import org.springframework.lang.Nullable;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* @author Christoph Strobl
* @author Johannes Englmeier
@ -316,7 +318,8 @@ class RepositoryMethodInvokerUnitTests { @@ -316,7 +318,8 @@ class RepositoryMethodInvokerUnitTests {
RepositoryMethodInvokerStub(Class<?> repositoryInterface, RepositoryInvocationMulticaster multicaster,
String methodName, Invokable invokable) {
super(methodByName(repositoryInterface, methodName), invokable);
super(DefaultRepositoryMethodMetadata.repositoryMethodMetadata(mock(RepositoryMetadata.class), methodByName(repositoryInterface, methodName)), invokable);
this.repositoryInterface = repositoryInterface;
this.multicaster = multicaster;
}

Loading…
Cancel
Save