From f7895eb3065e753f3a353ba76d0d3cce86c8b876 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Thu, 29 Aug 2024 13:55:19 +0200 Subject: [PATCH] Add `RepositoryMetadataAccess`. Repositories that detect fragment implementations implementing RepositoryMetadataAccess enable metadata exposure regardless of the exposeMetadata flag. See: #3090 Original Pull Request: #3145 --- .../support/RepositoryFactorySupport.java | 26 +++++++++++--- .../support/RepositoryMetadataAccess.java | 34 +++++++++++++++++++ .../RepositoryFactorySupportUnitTests.java | 21 ++++++++++++ 3 files changed, 76 insertions(+), 5 deletions(-) create mode 100644 src/main/java/org/springframework/data/repository/core/support/RepositoryMetadataAccess.java diff --git a/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java b/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java index 88965a4ab..650c5aefd 100644 --- a/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java +++ b/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java @@ -127,10 +127,16 @@ public abstract class RepositoryFactorySupport implements BeanClassLoaderAware, * retrieval via the {@code RepositoryMethodContext} class. This is useful if an advised object needs to obtain * repository information. *

- * Default is {@literal "false"}, in order to avoid unnecessary extra interception. This means that no guarantees are provided - * that {@code RepositoryMethodContext} access will work consistently within any method of the advised object. - * - * @since 3.4.0 + * Default is {@literal "false"}, in order to avoid unnecessary extra interception. This means that no guarantees are + * provided that {@code RepositoryMethodContext} access will work consistently within any method of the advised + * object. + *

+ * Repository method metadata is also exposed if implementations within the {@link RepositoryFragments repository + * composition} implement {@link RepositoryMetadataAccess}. + * + * @since 3.4 + * @see RepositoryMethodContext + * @see RepositoryMetadataAccess */ public void setExposeMetadata(boolean exposeMetadata) { this.exposeMetadata = exposeMetadata; @@ -345,7 +351,7 @@ public abstract class RepositoryFactorySupport implements BeanClassLoaderAware, result.addAdvice(new MethodInvocationValidator()); } - if (this.exposeMetadata) { + if (this.exposeMetadata || shouldExposeMetadata(fragments)) { result.addAdvice(new ExposeMetadataInterceptor(metadata)); result.addAdvisor(ExposeInvocationInterceptor.ADVISOR); } @@ -616,6 +622,16 @@ public abstract class RepositoryFactorySupport implements BeanClassLoaderAware, return Lazy.of(() -> getProjectionFactory(this.classLoader, this.beanFactory)); } + private static boolean shouldExposeMetadata(RepositoryFragments fragments) { + + for (RepositoryFragment fragment : fragments) { + if (fragment.getImplementation().filter(RepositoryMetadataAccess.class::isInstance).isPresent()) { + return true; + } + } + return false; + } + /** * Method interceptor that calls methods on the {@link RepositoryComposition}. * diff --git a/src/main/java/org/springframework/data/repository/core/support/RepositoryMetadataAccess.java b/src/main/java/org/springframework/data/repository/core/support/RepositoryMetadataAccess.java new file mode 100644 index 000000000..24f3353d2 --- /dev/null +++ b/src/main/java/org/springframework/data/repository/core/support/RepositoryMetadataAccess.java @@ -0,0 +1,34 @@ +/* + * 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; + +/** + * Marker for repository fragment implementation that intend to access repository method invocation metadata. + *

+ * Note that this is a marker interface in the style of {@link java.io.Serializable}, semantically applying to a + * fragment implementation class rather. In other words, this marker applies to a particular repository composition that + * enables metadata access for the repository proxy when the composition contain fragments implementing this interface. + *

+ * Ideally, in a repository composition only the fragment implementation uses this interface while the fragment + * interface does not. + * + * @author Mark Paluch + * @since 3.4 + * @see RepositoryMethodContext + */ +public interface RepositoryMetadataAccess { + +} diff --git a/src/test/java/org/springframework/data/repository/core/support/RepositoryFactorySupportUnitTests.java b/src/test/java/org/springframework/data/repository/core/support/RepositoryFactorySupportUnitTests.java index 79f2862bf..2661998cb 100755 --- a/src/test/java/org/springframework/data/repository/core/support/RepositoryFactorySupportUnitTests.java +++ b/src/test/java/org/springframework/data/repository/core/support/RepositoryFactorySupportUnitTests.java @@ -271,6 +271,27 @@ class RepositoryFactorySupportUnitTests { assertThat(metadata.methodInvocation().getMethod().getName()).isEqualTo("findMetadataByLastname"); } + @Test // GH-3090 + void capturesRepositoryMetadataWithMetadataAccess() { + + record Metadata(RepositoryMethodContext context, MethodInvocation methodInvocation) { + } + + when(factory.queryOne.execute(any(Object[].class))) + .then(invocation -> new Metadata(RepositoryMethodContext.currentMethod(), + ExposeInvocationInterceptor.currentInvocation())); + + var repository = factory.getRepository(ObjectRepository.class, new RepositoryMetadataAccess() {}); + var metadataByLastname = repository.findMetadataByLastname(); + + assertThat(metadataByLastname).isInstanceOf(Metadata.class); + + Metadata metadata = (Metadata) metadataByLastname; + assertThat(metadata.context().getMethod().getName()).isEqualTo("findMetadataByLastname"); + assertThat(metadata.context().getRepository().getDomainType()).isEqualTo(Object.class); + assertThat(metadata.methodInvocation().getMethod().getName()).isEqualTo("findMetadataByLastname"); + } + @Test // DATACMNS-509, DATACMNS-1764 void convertsWithSameElementType() {