Skip to content

Commit

Permalink
Polishing.
Browse files Browse the repository at this point in the history
Avoid nullability in RepositoryMethodContextHolder.getContext(). Introduce shortcut in RepositoryMethodContext to obtain the current thread-local context. Update documentation.
  • Loading branch information
mp911de committed Oct 16, 2024
1 parent 6e9e325 commit 032515d
Show file tree
Hide file tree
Showing 8 changed files with 76 additions and 147 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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<T> implements SearchExtension<T> {
Expand All @@ -282,7 +282,7 @@ class DefaultSearchExtension<T> implements SearchExtension<T> {
}
public List<T> search(String text, Limit limit) {
return search(RepositoryMethodContext.currentMethod(), text, limit);
return search(RepositoryMethodContext.getContext(), text, limit);
}
List<T> search(RepositoryMethodContext metadata, String text, Limit limit) {
Expand All @@ -297,12 +297,12 @@ class DefaultSearchExtension<T> implements SearchExtension<T> {
}
----

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]
Expand All @@ -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<T> implements SearchExtension<T>, RepositoryMetadataAccess {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,74 +19,68 @@
import org.springframework.lang.Nullable;

/**
* Associates a given {@link RepositoryMethodContext} with the current execution thread.
* <p>
* 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<RepositoryMethodContext> currentMethod = new NamedThreadLocal<>(
"Current Repository Method");

/**
* Make the given repository method metadata available via the {@link #getContext()} method.
* <p>
* 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<RepositoryMethodContext> 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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
*
* @author Mark Paluch
* @since 3.4
* @see RepositoryMethodContext
* @see org.springframework.data.repository.core.RepositoryMethodContext
*/
public interface RepositoryMetadataAccess {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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() {});
Expand Down

0 comments on commit 032515d

Please sign in to comment.