diff --git a/src/main/antora/modules/ROOT/pages/repositories/custom-implementations.adoc b/src/main/antora/modules/ROOT/pages/repositories/custom-implementations.adoc index 1d85f90e47..d279a359d8 100644 --- a/src/main/antora/modules/ROOT/pages/repositories/custom-implementations.adoc +++ b/src/main/antora/modules/ROOT/pages/repositories/custom-implementations.adoc @@ -271,7 +271,7 @@ package com.acme.search; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Limit; -import org.springframework.data.repository.core.support.RepositoryMethodContext; +import org.springframework.data.repository.core.RepositoryMethodContext; class DefaultSearchExtension implements SearchExtension { @@ -282,7 +282,7 @@ class DefaultSearchExtension implements SearchExtension { } public List search(String text, Limit limit) { - return search(RepositoryMethodContext.currentMethod(), text, limit); + return search(RepositoryMethodContext.getContext(), text, limit); } List search(RepositoryMethodContext metadata, String text, Limit limit) { @@ -297,12 +297,12 @@ class DefaultSearchExtension implements SearchExtension { } ---- -In the example above `RepositoryMethodContext.currentMethod()` is used to retrieve metadata for the actual method invocation. +In the example above `RepositoryMethodContext.getContext()` is used to retrieve metadata for the actual method invocation. `RepositoryMethodContext` exposes information attached to the repository such as the domain type. In this case we use the repository domain type to identify the name of the index to be searched. Exposing invocation metadata is costly, hence it is disabled by default. -To access `RepositoryMethodContext.currentMethod()` you need to advise the repository factory responsible for creating the actual repository to expose method metadata. +To access `RepositoryMethodContext.getContext()` you need to advise the repository factory responsible for creating the actual repository to expose method metadata. .Expose Repository Metadata [tabs] @@ -319,7 +319,7 @@ package com.acme.search; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Limit; import org.springframework.data.repository.core.support.RepositoryMetadataAccess; -import org.springframework.data.repository.core.support.RepositoryMethodContext; +import org.springframework.data.repository.core.RepositoryMethodContext; class DefaultSearchExtension implements SearchExtension, RepositoryMetadataAccess { diff --git a/src/main/java/org/springframework/data/repository/core/RepositoryMethodContext.java b/src/main/java/org/springframework/data/repository/core/RepositoryMethodContext.java index 12c7829ecf..bef865c893 100644 --- a/src/main/java/org/springframework/data/repository/core/RepositoryMethodContext.java +++ b/src/main/java/org/springframework/data/repository/core/RepositoryMethodContext.java @@ -33,10 +33,24 @@ * @author Christoph Strobl * @author Mark Paluch * @author Oliver Drotbohm - * @since 3.4.0 + * @since 3.4 */ public interface RepositoryMethodContext { + /** + * Try to return the current repository method metadata. This method is usable only if the calling method has been + * invoked via a repository method, and the repository factory has been set to expose metadata. Otherwise, this method + * will throw an IllegalStateException. + * + * @return the current repository method metadata (never returns {@code null}) + * @throws IllegalStateException if the repository method metadata cannot be found, because the method was invoked + * outside a repository method invocation context, or because the repository has not been configured to + * expose its metadata. + */ + static RepositoryMethodContext getContext() throws IllegalStateException { + return RepositoryMethodContextHolder.getContext(); + } + /** * Returns the metadata for the repository. * diff --git a/src/main/java/org/springframework/data/repository/core/RepositoryMethodContextHolder.java b/src/main/java/org/springframework/data/repository/core/RepositoryMethodContextHolder.java index bf3fda56d9..bc57f152f7 100644 --- a/src/main/java/org/springframework/data/repository/core/RepositoryMethodContextHolder.java +++ b/src/main/java/org/springframework/data/repository/core/RepositoryMethodContextHolder.java @@ -19,74 +19,68 @@ import org.springframework.lang.Nullable; /** + * Associates a given {@link RepositoryMethodContext} with the current execution thread. + *

+ * This class provides a series of static methods that interact with a thread-local storage of + * {@link RepositoryMethodContext}. The purpose of the class is to provide a convenient way to be used for an + * application. + * * @author Christoph Strobl - * @since 3.4.0 + * @author Mark Paluch + * @since 3.4 + * @see RepositoryMethodContext */ public class RepositoryMethodContextHolder { - private static ContextProvider contextSupplier; - - static { - contextSupplier = new ThreadLocalContextProvider(); - } - + /** + * ThreadLocal holder for repository method associated with this thread. Will contain {@code null} unless the + * "exposeMetadata" property on the controlling repository factory configuration has been set to {@code true}. + */ + private static final ThreadLocal currentMethod = new NamedThreadLocal<>( + "Current Repository Method"); + + /** + * Make the given repository method metadata available via the {@link #getContext()} method. + *

+ * Note that the caller should be careful to keep the old value as appropriate. + * + * @param context the metadata to expose (or {@code null} to reset it) + * @return the old metadata, which may be {@code null} if none was bound + * @see #getContext() + */ @Nullable public static RepositoryMethodContext setContext(@Nullable RepositoryMethodContext context) { - return contextSupplier.set(context); - } - - @Nullable - public static RepositoryMethodContext current() { - return contextSupplier.get(); - } - - public static void clearContext() { - contextSupplier.clear(); - } - - interface ContextProvider { - - @Nullable - RepositoryMethodContext get(); - @Nullable - RepositoryMethodContext set(@Nullable RepositoryMethodContext context); - - void clear(); - } - - static class ThreadLocalContextProvider implements ContextProvider { - - /** - * ThreadLocal holder for repository method associated with this thread. Will contain {@code null} unless the - * "exposeMetadata" property on the controlling repository factory configuration has been set to "true". - */ - private static final ThreadLocal currentMethod = new NamedThreadLocal<>( - "Current Repository Method"); - - @Override - @Nullable - public RepositoryMethodContext get() { - return currentMethod.get(); - } - - public void clear() { + RepositoryMethodContext old = currentMethod.get(); + if (context != null) { + currentMethod.set(context); + } else { currentMethod.remove(); } - @Override - @Nullable - public RepositoryMethodContext set(@Nullable RepositoryMethodContext context) { - - RepositoryMethodContext old = currentMethod.get(); - - if (context != null) { - currentMethod.set(context); - } else { - currentMethod.remove(); - } + return old; + } - return old; + /** + * Try to return the current repository method metadata. This method is usable only if the calling method has been + * invoked via a repository method, and the repository factory has been set to expose metadata. Otherwise, this method + * will throw an IllegalStateException. + * + * @return the current repository method metadata (never returns {@code null}) + * @throws IllegalStateException if the repository method metadata cannot be found, because the method was invoked + * outside a repository method invocation context, or because the repository has not been configured to + * expose its metadata. + */ + public static RepositoryMethodContext getContext() { + + RepositoryMethodContext metadata = currentMethod.get(); + + if (metadata == null) { + throw new IllegalStateException( + "Cannot find current repository method: Set 'exposeMetadata' property on RepositoryFactorySupport to 'true' to make it available, and " + + "ensure that RepositoryMethodContext.getContext() is invoked in the same thread as the repository invocation."); } + + return metadata; } } diff --git a/src/main/java/org/springframework/data/repository/core/support/DefaultRepositoryMethodContext.java b/src/main/java/org/springframework/data/repository/core/support/DefaultRepositoryMethodContext.java index 3d02cc2883..13e14f6ea1 100644 --- a/src/main/java/org/springframework/data/repository/core/support/DefaultRepositoryMethodContext.java +++ b/src/main/java/org/springframework/data/repository/core/support/DefaultRepositoryMethodContext.java @@ -27,7 +27,7 @@ * @author Christoph Strobl * @author Mark Paluch * @author Oliver Drotbohm - * @since 3.4.0 + * @since 3.4 */ public class DefaultRepositoryMethodContext implements RepositoryMethodContext { diff --git a/src/main/java/org/springframework/data/repository/core/support/DynamicLookupTargetSource.java b/src/main/java/org/springframework/data/repository/core/support/DynamicLookupTargetSource.java deleted file mode 100644 index 0a7023014f..0000000000 --- a/src/main/java/org/springframework/data/repository/core/support/DynamicLookupTargetSource.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * 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.util.function.Supplier; - -import org.springframework.aop.TargetSource; -import org.springframework.lang.NonNull; -import org.springframework.lang.Nullable; -import org.springframework.util.Assert; - -/** - * A {@link TargetSource}, that will re-obtain an instance using the configured supplier. - * - * @author Oliver Drotbohm - * @since 3.4.0 - */ -class DynamicLookupTargetSource implements TargetSource { - - private final Class type; - private final Supplier supplier; - - /** - * Creates a new {@link DynamicLookupTargetSource} for the given type and {@link Supplier}. - * - * @param type must not be {@literal null}. - * @param supplier must not be {@literal null}. - */ - public DynamicLookupTargetSource(Class type, Supplier supplier) { - - Assert.notNull(type, "Type must not be null!"); - Assert.notNull(supplier, "Supplier must not be null!"); - - this.type = type; - this.supplier = supplier; - } - - /* - * (non-Javadoc) - * @see org.springframework.aop.TargetSource#isStatic() - */ - @Override - public boolean isStatic() { - return false; - } - - /* - * (non-Javadoc) - * @see org.springframework.aop.TargetSource#getTarget() - */ - @Override - @Nullable - public Object getTarget() throws Exception { - return supplier.get(); - } - - /* - * (non-Javadoc) - * @see org.springframework.aop.TargetSource#getTargetClass() - */ - @Override - @NonNull - public Class getTargetClass() { - return type; - } -} diff --git a/src/main/java/org/springframework/data/repository/core/support/RepositoryFactoryBeanSupport.java b/src/main/java/org/springframework/data/repository/core/support/RepositoryFactoryBeanSupport.java index 967d02d158..3059bbf209 100644 --- a/src/main/java/org/springframework/data/repository/core/support/RepositoryFactoryBeanSupport.java +++ b/src/main/java/org/springframework/data/repository/core/support/RepositoryFactoryBeanSupport.java @@ -121,7 +121,7 @@ public void setRepositoryBaseClass(Class repositoryBaseClass) { * Default is "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 + * @since 3.4 */ public void setExposeMetadata(boolean exposeMetadata) { this.exposeMetadata = exposeMetadata; 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 index fb4a7b82ce..4122e20617 100644 --- a/src/main/java/org/springframework/data/repository/core/support/RepositoryMetadataAccess.java +++ b/src/main/java/org/springframework/data/repository/core/support/RepositoryMetadataAccess.java @@ -27,7 +27,7 @@ * * @author Mark Paluch * @since 3.4 - * @see RepositoryMethodContext + * @see org.springframework.data.repository.core.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 6de556045d..f6371c1506 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 @@ -256,7 +256,7 @@ void capturesRepositoryMetadata() { record Metadata(RepositoryMethodContext context, MethodInvocation methodInvocation) {} when(factory.queryOne.execute(any(Object[].class))) - .then(invocation -> new Metadata(RepositoryMethodContextHolder.current(), + .then(invocation -> new Metadata(RepositoryMethodContextHolder.getContext(), ExposeInvocationInterceptor.currentInvocation())); factory.setExposeMetadata(true); @@ -279,7 +279,7 @@ record Metadata(RepositoryMethodContext context, MethodInvocation methodInvocati } when(factory.queryOne.execute(any(Object[].class))) - .then(invocation -> new Metadata(RepositoryMethodContextHolder.current(), + .then(invocation -> new Metadata(RepositoryMethodContextHolder.getContext(), ExposeInvocationInterceptor.currentInvocation())); var repository = factory.getRepository(ObjectRepository.class, new RepositoryMetadataAccess() {});