DATACMNS-1763 - Allow registration of QueryMethod invocation listeners.

We now allow for registering RepositoryMethodInvocationListeners to notify listeners upon repository method invocations (query methods and repository fragments). Listeners are notified upon method completion reporting the repository interface, the invoked methods, duration, outcome and arguments.

Original Pull Request: #455
This commit is contained in:
Mark Paluch
2020-07-08 15:40:18 +02:00
committed by Christoph Strobl
parent d840057b0c
commit e77947f0a5
11 changed files with 654 additions and 202 deletions

View File

@@ -50,14 +50,14 @@ class QueryExecutorMethodInterceptorUnitTests {
assertThatIllegalStateException()
.isThrownBy(() -> new QueryExecutorMethodInterceptor(information, new SpelAwareProxyProjectionFactory(),
Optional.empty(), PropertiesBasedNamedQueries.EMPTY, Collections.emptyList()));
Optional.empty(), PropertiesBasedNamedQueries.EMPTY, Collections.emptyList(), Collections.emptyList()));
}
@Test // DATACMNS-1508
void skipsQueryLookupsIfQueryLookupStrategyIsNotPresent() {
new QueryExecutorMethodInterceptor(information, new SpelAwareProxyProjectionFactory(), Optional.empty(),
PropertiesBasedNamedQueries.EMPTY, Collections.emptyList());
PropertiesBasedNamedQueries.EMPTY, Collections.emptyList(), Collections.emptyList());
verify(strategy, times(0)).resolveQuery(any(), any(), any(), any());
}

View File

@@ -29,10 +29,12 @@ import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
@@ -86,6 +88,7 @@ class RepositoryFactorySupportUnitTests {
@Mock PlainQueryCreationListener otherListener;
@Mock RepositoryProxyPostProcessor repositoryPostProcessor;
@Mock RepositoryMethodInvocationListener invocationListener;
@BeforeEach
void setUp() {
@@ -118,13 +121,16 @@ class RepositoryFactorySupportUnitTests {
verify(repositoryPostProcessor, times(1)).postProcess(any(ProxyFactory.class), any(RepositoryInformation.class));
}
@Test
@Test // DATACMNS-1764
void routesCallToRedeclaredMethodIntoTarget() {
factory.addInvocationListener(invocationListener);
ObjectRepository repository = factory.getRepository(ObjectRepository.class);
repository.save(repository);
verify(backingRepo, times(1)).save(any(Object.class));
verify(invocationListener).afterInvocation(any());
}
@Test
@@ -177,7 +183,7 @@ class RepositoryFactorySupportUnitTests {
assertThat(ReflectionTestUtils.getField(factory, "classLoader")).isEqualTo(ClassUtils.getDefaultClassLoader());
}
@Test // DATACMNS-489
@Test // DATACMNS-489, DATACMNS-1764
void wrapsExecutionResultIntoFutureIfConfigured() throws Exception {
final Object reference = new Object();
@@ -187,6 +193,8 @@ class RepositoryFactorySupportUnitTests {
return reference;
});
factory.addInvocationListener(invocationListener);
ConvertingRepository repository = factory.getRepository(ConvertingRepository.class);
AsyncAnnotationBeanPostProcessor processor = new AsyncAnnotationBeanPostProcessor();
@@ -204,13 +212,44 @@ class RepositoryFactorySupportUnitTests {
assertThat(future.get()).isEqualTo(reference);
verify(factory.queryOne, times(1)).execute(any(Object[].class));
ArgumentCaptor<RepositoryMethodInvocationListener.Invocation> captor = ArgumentCaptor
.forClass(RepositoryMethodInvocationListener.Invocation.class);
verify(invocationListener).afterInvocation(captor.capture());
RepositoryMethodInvocationListener.Invocation value = captor.getValue();
assertThat(value.getResult()).isEqualTo(reference);
}
@Test // DATACMNS-509
@Test // DATACMNS-1764, DATACMNS-1764
void capturesFailureFromInvocation() {
when(factory.queryOne.execute(any(Object[].class))).thenThrow(new IllegalStateException());
factory.addInvocationListener(invocationListener);
ConvertingRepository repository = factory.getRepository(ConvertingRepository.class);
try {
repository.findByLastname("Foo");
fail("Missing exception");
} catch (IllegalStateException e) {}
ArgumentCaptor<RepositoryMethodInvocationListener.Invocation> captor = ArgumentCaptor
.forClass(RepositoryMethodInvocationListener.Invocation.class);
verify(invocationListener).afterInvocation(captor.capture());
RepositoryMethodInvocationListener.Invocation invocation = captor.getValue();
assertThat(invocation.getDuration(TimeUnit.NANOSECONDS)).isGreaterThan(0);
assertThat(invocation.getException()).isInstanceOf(IllegalStateException.class);
}
@Test // DATACMNS-509, DATACMNS-1764
void convertsWithSameElementType() {
List<String> names = singletonList("Dave");
factory.addInvocationListener(invocationListener);
when(factory.queryOne.execute(any(Object[].class))).thenReturn(names);
ConvertingRepository repository = factory.getRepository(ConvertingRepository.class);
@@ -218,6 +257,13 @@ class RepositoryFactorySupportUnitTests {
assertThat(result).hasSize(1);
assertThat(result.iterator().next()).isEqualTo("Dave");
ArgumentCaptor<RepositoryMethodInvocationListener.Invocation> captor = ArgumentCaptor
.forClass(RepositoryMethodInvocationListener.Invocation.class);
verify(invocationListener).afterInvocation(captor.capture());
RepositoryMethodInvocationListener.Invocation value = captor.getValue();
assertThat(value.getResult()).isEqualTo(names);
}
@Test // DATACMNS-509
@@ -461,6 +507,8 @@ class RepositoryFactorySupportUnitTests {
Set<Object> convertListToObjectSet();
Future<Object> findByLastname(String lastname);
@Async
Future<Object> findByFirstname(String firstname);