From c962c526f1452f40cd22ec7597e574e35cb01cf3 Mon Sep 17 00:00:00 2001 From: Nico Heller Date: Fri, 26 Aug 2022 09:13:34 +0200 Subject: [PATCH 1/3] Provide invocation arguments for RepositoryMethodInvocation --- .../RepositoryInvocationMulticaster.java | 2 +- .../RepositoryMethodInvocationListener.java | 11 +++- .../core/support/RepositoryMethodInvoker.java | 57 +++++++++++-------- .../RepositoryMethodInvokerUnitTests.java | 34 +++++++++++ 4 files changed, 77 insertions(+), 27 deletions(-) diff --git a/src/main/java/org/springframework/data/repository/core/support/RepositoryInvocationMulticaster.java b/src/main/java/org/springframework/data/repository/core/support/RepositoryInvocationMulticaster.java index 3e40bae3fc..fc008e2e9a 100644 --- a/src/main/java/org/springframework/data/repository/core/support/RepositoryInvocationMulticaster.java +++ b/src/main/java/org/springframework/data/repository/core/support/RepositoryInvocationMulticaster.java @@ -56,7 +56,7 @@ public void notifyListeners(Method method, Object[] args, RepositoryMethodInvoca /** * {@link RepositoryInvocationMulticaster} implementation that notifies {@link RepositoryMethodInvocationListener} - * upon {@link #notifyListeners(Method, Object[], RepositoryMethodInvocationResult)}. + * upon {@link #notifyListeners(Method, Object[], RepositoryMethodInvocation)}. * * @author Mark Paluch */ diff --git a/src/main/java/org/springframework/data/repository/core/support/RepositoryMethodInvocationListener.java b/src/main/java/org/springframework/data/repository/core/support/RepositoryMethodInvocationListener.java index 52f593236c..83b6a4a825 100644 --- a/src/main/java/org/springframework/data/repository/core/support/RepositoryMethodInvocationListener.java +++ b/src/main/java/org/springframework/data/repository/core/support/RepositoryMethodInvocationListener.java @@ -49,20 +49,25 @@ class RepositoryMethodInvocation { private final long durationNs; private final Class repositoryInterface; private final Method method; + + private final Object[] arguments; + private final RepositoryMethodInvocationResult result; /** * @param repositoryInterface the repository interface that was used to call {@link Method}. * @param method the actual method that was called. + * @param arguments the actual arguments provided to the repository method. * @param result the outcome of the invocation. Must not be {@literal null}. * @param durationNs the duration in {@link TimeUnit#NANOSECONDS}. */ public RepositoryMethodInvocation(Class repositoryInterface, Method method, - RepositoryMethodInvocationResult result, long durationNs) { + Object[] arguments, RepositoryMethodInvocationResult result, long durationNs) { this.durationNs = durationNs; this.repositoryInterface = repositoryInterface; this.method = method; + this.arguments = arguments; this.result = result; } @@ -81,6 +86,10 @@ public Method getMethod() { return method; } + public Object[] getArguments() { + return arguments; + } + @Nullable public RepositoryMethodInvocationResult getResult() { return result; diff --git a/src/main/java/org/springframework/data/repository/core/support/RepositoryMethodInvoker.java b/src/main/java/org/springframework/data/repository/core/support/RepositoryMethodInvoker.java index f3b3f886b7..bfafe8c435 100644 --- a/src/main/java/org/springframework/data/repository/core/support/RepositoryMethodInvoker.java +++ b/src/main/java/org/springframework/data/repository/core/support/RepositoryMethodInvoker.java @@ -130,7 +130,7 @@ private Object doInvoke(Class repositoryInterface, RepositoryInvocationMultic throws Exception { RepositoryMethodInvocationCaptor invocationResultCaptor = RepositoryMethodInvocationCaptor - .captureInvocationOn(repositoryInterface); + .captureInvocationOn(repositoryInterface, args); try { @@ -142,14 +142,14 @@ private Object doInvoke(Class repositoryInterface, RepositoryInvocationMultic if (result instanceof Stream) { return ((Stream) result).onClose( - () -> multicaster.notifyListeners(method, args, computeInvocationResult(invocationResultCaptor.success()))); + () -> multicaster.notifyListeners(method, args, computeInvocationResult(invocationResultCaptor.success(args)))); } - multicaster.notifyListeners(method, args, computeInvocationResult(invocationResultCaptor.success())); + multicaster.notifyListeners(method, args, computeInvocationResult(invocationResultCaptor.success(args))); return result; } catch (Exception e) { - multicaster.notifyListeners(method, args, computeInvocationResult(invocationResultCaptor.error(e))); + multicaster.notifyListeners(method, args, computeInvocationResult(invocationResultCaptor.error(e, args))); throw e; } } @@ -168,7 +168,7 @@ private Object doInvokeReactiveToSuspended(Class repositoryInterface, Reposit args[args.length - 1] = null; RepositoryMethodInvocationCaptor invocationResultCaptor = RepositoryMethodInvocationCaptor - .captureInvocationOn(repositoryInterface); + .captureInvocationOn(repositoryInterface, args); try { Publisher result = new ReactiveInvocationListenerDecorator().decorate(repositoryInterface, multicaster, args, @@ -184,7 +184,7 @@ private Object doInvokeReactiveToSuspended(Class repositoryInterface, Reposit return AwaitKt.awaitSingleOrNull(result, continuation); } catch (Exception e) { - multicaster.notifyListeners(method, args, computeInvocationResult(invocationResultCaptor.error(e))); + multicaster.notifyListeners(method, args, computeInvocationResult(invocationResultCaptor.error(e, args))); throw e; } } @@ -195,8 +195,8 @@ private static Object collectToList(Object result) { } private RepositoryMethodInvocation computeInvocationResult(RepositoryMethodInvocationCaptor captured) { - return new RepositoryMethodInvocation(captured.getRepositoryInterface(), method, captured.getCapturedResult(), - captured.getDuration()); + return new RepositoryMethodInvocation(captured.getRepositoryInterface(), method, captured.getArguments(), + captured.getCapturedResult(), captured.getDuration()); } interface Invokable { @@ -224,34 +224,34 @@ Publisher decorate(Class repositoryInterface, RepositoryInvocationMul if (result instanceof Mono) { return Mono.usingWhen( - Mono.fromSupplier(() -> RepositoryMethodInvocationCaptor.captureInvocationOn(repositoryInterface)), it -> { + Mono.fromSupplier(() -> RepositoryMethodInvocationCaptor.captureInvocationOn(repositoryInterface, args)), it -> { it.trackStart(); return ReactiveWrapperConverters.toWrapper(result, Mono.class); }, it -> { - multicaster.notifyListeners(method, args, computeInvocationResult(it.success())); + multicaster.notifyListeners(method, args, computeInvocationResult(it.success(args))); return Mono.empty(); }, (it, e) -> { - multicaster.notifyListeners(method, args, computeInvocationResult(it.error(e))); + multicaster.notifyListeners(method, args, computeInvocationResult(it.error(e, args))); return Mono.empty(); }, it -> { - multicaster.notifyListeners(method, args, computeInvocationResult(it.canceled())); + multicaster.notifyListeners(method, args, computeInvocationResult(it.canceled(args))); return Mono.empty(); }); } return Flux.usingWhen( - Mono.fromSupplier(() -> RepositoryMethodInvocationCaptor.captureInvocationOn(repositoryInterface)), it -> { + Mono.fromSupplier(() -> RepositoryMethodInvocationCaptor.captureInvocationOn(repositoryInterface, args)), it -> { it.trackStart(); return result instanceof Publisher ? (Publisher) result : ReactiveWrapperConverters.toWrapper(result, Publisher.class); }, it -> { - multicaster.notifyListeners(method, args, computeInvocationResult(it.success())); + multicaster.notifyListeners(method, args, computeInvocationResult(it.success(args))); return Mono.empty(); }, (it, e) -> { - multicaster.notifyListeners(method, args, computeInvocationResult(it.error(e))); + multicaster.notifyListeners(method, args, computeInvocationResult(it.error(e, args))); return Mono.empty(); }, it -> { - multicaster.notifyListeners(method, args, computeInvocationResult(it.canceled())); + multicaster.notifyListeners(method, args, computeInvocationResult(it.canceled(args))); return Mono.empty(); }); } @@ -378,33 +378,36 @@ private static class RepositoryMethodInvocationCaptor { private final State state; private final @Nullable Throwable error; + private final Object[] arguments; + protected RepositoryMethodInvocationCaptor(Class repositoryInterface, long startTime, Long endTime, State state, - @Nullable Throwable exception) { + @Nullable Throwable exception, Object[] arguments) { this.repositoryInterface = repositoryInterface; this.startTime = startTime; this.endTime = endTime; this.state = state; this.error = exception instanceof InvocationTargetException ? exception.getCause() : exception; + this.arguments = arguments; } - public static RepositoryMethodInvocationCaptor captureInvocationOn(Class repositoryInterface) { - return new RepositoryMethodInvocationCaptor(repositoryInterface, System.nanoTime(), null, State.RUNNING, null); + public static RepositoryMethodInvocationCaptor captureInvocationOn(Class repositoryInterface, Object[] arguments) { + return new RepositoryMethodInvocationCaptor(repositoryInterface, System.nanoTime(), null, State.RUNNING, null, arguments); } - public RepositoryMethodInvocationCaptor error(Throwable exception) { + public RepositoryMethodInvocationCaptor error(Throwable exception, Object[] arguments) { return new RepositoryMethodInvocationCaptor(repositoryInterface, startTime, System.nanoTime(), State.ERROR, - exception); + exception, arguments); } - public RepositoryMethodInvocationCaptor success() { + public RepositoryMethodInvocationCaptor success(Object[] arguments) { return new RepositoryMethodInvocationCaptor(repositoryInterface, startTime, System.nanoTime(), State.SUCCESS, - null); + null, arguments); } - public RepositoryMethodInvocationCaptor canceled() { + public RepositoryMethodInvocationCaptor canceled(Object[] arguments) { return new RepositoryMethodInvocationCaptor(repositoryInterface, startTime, System.nanoTime(), State.CANCELED, - null); + null, arguments); } Class getRepositoryInterface() { @@ -424,6 +427,10 @@ public Throwable getError() { return error; } + public Object[] getArguments() { + return arguments; + } + long getDuration() { return (endTime != null ? endTime : System.nanoTime()) - startTime; } diff --git a/src/test/java/org/springframework/data/repository/core/support/RepositoryMethodInvokerUnitTests.java b/src/test/java/org/springframework/data/repository/core/support/RepositoryMethodInvokerUnitTests.java index 5e116e830b..35ffb8852c 100644 --- a/src/test/java/org/springframework/data/repository/core/support/RepositoryMethodInvokerUnitTests.java +++ b/src/test/java/org/springframework/data/repository/core/support/RepositoryMethodInvokerUnitTests.java @@ -197,6 +197,7 @@ void capturesImperativeSuccessCorrectly() throws Exception { assertThat(multicaster.first().getResult().getState()).isEqualTo(State.SUCCESS); assertThat(multicaster.first().getResult().getError()).isNull(); + assertThat(multicaster.first().getArguments()).isNotNull(); } @Test // DATACMNS-1764 @@ -208,6 +209,7 @@ void capturesReactiveCompletionCorrectly() throws Exception { assertThat(multicaster.first().getResult().getState()).isEqualTo(State.SUCCESS); assertThat(multicaster.first().getResult().getError()).isNull(); + assertThat(multicaster.first().getArguments()).isNotNull(); } @Test // DATACMNS-1764 @@ -218,6 +220,7 @@ void capturesImperativeErrorCorrectly() { assertThat(multicaster.first().getResult().getState()).isEqualTo(State.ERROR); assertThat(multicaster.first().getResult().getError()).isInstanceOf(IllegalStateException.class); + assertThat(multicaster.first().getArguments()).isNotNull(); } @Test // DATACMNS-1764 @@ -231,6 +234,7 @@ void capturesReactiveErrorCorrectly() throws Exception { assertThat(multicaster.first().getResult().getState()).isEqualTo(State.ERROR); assertThat(multicaster.first().getResult().getError()).isInstanceOf(IllegalStateException.class); + assertThat(multicaster.first().getArguments()).isNotNull(); } @Test // DATACMNS-1764 @@ -242,6 +246,31 @@ void capturesReactiveCancellationCorrectly() throws Exception { assertThat(multicaster.first().getResult().getState()).isEqualTo(State.CANCELED); assertThat(multicaster.first().getResult().getError()).isNull(); + assertThat(multicaster.first().getArguments()).isNotNull(); + } + + @Test + void capturesImperativeArgumentsCorrectly() throws Exception { + String id = "id"; + String name = "name"; + + repositoryMethodInvoker("findByMultipleParameters").invoke(id, name); + + assertThat(multicaster.first().getArguments()).isNotNull(); + assertThat(multicaster.first().getArguments()).containsExactly(id, name); + } + + @Test + void capturesReactiveArgumentsCorrectly() throws Exception { + String id = "id"; + String name = "name"; + + when(query.execute(any())).thenReturn(Mono.just(new TestDummy())); + + repositoryMethodInvokerForReactive("findByMultipleParameters").> invoke(id, name).subscribe(); + + assertThat(multicaster.first().getArguments()).isNotNull(); + assertThat(multicaster.first().getArguments()).containsExactly(id, name); } @Test // DATACMNS-1764 @@ -273,6 +302,7 @@ public void resumeWith(@NotNull Object o) { assertThat(multicaster.first().getResult().getState()).isEqualTo(State.SUCCESS); assertThat(multicaster.first().getResult().getError()).isNull(); + assertThat(multicaster.first().getArguments()).isNotNull(); } RepositoryMethodInvokerStub repositoryMethodInvoker(String methodName) { @@ -398,11 +428,15 @@ interface DummyRepository extends CrudRepository { TestDummy findByName(String name); Stream streamAll(); + + TestDummy findByMultipleParameters(String id, String name); } interface ReactiveDummyRepository extends ReactiveCrudRepository { Mono findByName(String name); + + Mono findByMultipleParameters(String id, String name); } @ToString From 7d0e749a4d8315614327fe8c63ee57e87cd90378 Mon Sep 17 00:00:00 2001 From: Nico Heller Date: Fri, 26 Aug 2022 09:23:02 +0200 Subject: [PATCH 2/3] Cleanup unwanted whitespace --- .../core/support/RepositoryMethodInvocationListener.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/org/springframework/data/repository/core/support/RepositoryMethodInvocationListener.java b/src/main/java/org/springframework/data/repository/core/support/RepositoryMethodInvocationListener.java index 83b6a4a825..c74652d3b3 100644 --- a/src/main/java/org/springframework/data/repository/core/support/RepositoryMethodInvocationListener.java +++ b/src/main/java/org/springframework/data/repository/core/support/RepositoryMethodInvocationListener.java @@ -49,9 +49,7 @@ class RepositoryMethodInvocation { private final long durationNs; private final Class repositoryInterface; private final Method method; - private final Object[] arguments; - private final RepositoryMethodInvocationResult result; /** From c5d81ad606445693df5b751818e69b4548c687c8 Mon Sep 17 00:00:00 2001 From: Nico Heller Date: Fri, 26 Aug 2022 09:33:52 +0200 Subject: [PATCH 3/3] More unwanted newline cleanup --- .../data/repository/core/support/RepositoryMethodInvoker.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/org/springframework/data/repository/core/support/RepositoryMethodInvoker.java b/src/main/java/org/springframework/data/repository/core/support/RepositoryMethodInvoker.java index bfafe8c435..bd6a5edb26 100644 --- a/src/main/java/org/springframework/data/repository/core/support/RepositoryMethodInvoker.java +++ b/src/main/java/org/springframework/data/repository/core/support/RepositoryMethodInvoker.java @@ -377,7 +377,6 @@ private static class RepositoryMethodInvocationCaptor { private @Nullable Long endTime; private final State state; private final @Nullable Throwable error; - private final Object[] arguments; protected RepositoryMethodInvocationCaptor(Class repositoryInterface, long startTime, Long endTime, State state,