From be343019d66a47c23542874f835b9c0318ca4c92 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Wed, 16 Oct 2024 09:46:49 +0200 Subject: [PATCH] Polishing. Avoid nullability in RepositoryMethodContextHolder.getContext(). Introduce shortcut in RepositoryMethodContext to obtain the current thread-local context. --- .../core/RepositoryMethodContext.java | 16 ++- .../core/RepositoryMethodContextHolder.java | 108 +++++++++--------- .../DefaultRepositoryMethodContext.java | 2 +- .../support/DynamicLookupTargetSource.java | 79 ------------- .../support/RepositoryFactoryBeanSupport.java | 2 +- .../RepositoryFactorySupportUnitTests.java | 4 +- 6 files changed, 70 insertions(+), 141 deletions(-) delete mode 100644 src/main/java/org/springframework/data/repository/core/support/DynamicLookupTargetSource.java 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..67337f75f8 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.currentMethod() 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/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() {});