Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons</artifactId>
<version>3.4.0-SNAPSHOT</version>
<version>3.4.0-GH-3175-SNAPSHOT</version>

<name>Spring Data Core</name>
<description>Core Spring concepts underpinning every Spring Data module.</description>
Expand Down
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 @@ -18,11 +18,14 @@
import java.util.Arrays;
import java.util.Properties;

import org.springframework.aop.SpringProxy;
import org.springframework.aop.framework.Advised;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.aot.hint.TypeReference;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.core.DecoratingProxy;
import org.springframework.core.io.InputStreamSource;
import org.springframework.data.domain.Example;
import org.springframework.data.mapping.context.MappingContext;
Expand Down Expand Up @@ -99,5 +102,13 @@ public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader)
// annotated queries
hints.proxies().registerJdkProxy( //
TypeReference.of("org.springframework.data.annotation.QueryAnnotation"));

registerSpringProxy(TypeReference.of("org.springframework.data.repository.core.RepositoryMethodContext"), hints);
}

private static void registerSpringProxy(TypeReference type, RuntimeHints runtimeHints) {

runtimeHints.proxies().registerJdkProxy(type, TypeReference.of(SpringProxy.class),
TypeReference.of(Advised.class), TypeReference.of(DecoratingProxy.class));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
*/
package org.springframework.data.repository.config;

import static org.springframework.beans.factory.support.BeanDefinitionReaderUtils.*;
import static org.springframework.beans.factory.support.BeanDefinitionReaderUtils.GENERATED_BEAN_NAME_SEPARATOR;
import static org.springframework.beans.factory.support.BeanDefinitionReaderUtils.generateBeanName;

import java.lang.annotation.Annotation;
import java.util.Collection;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.repository.core.support;
package org.springframework.data.repository.core;

import java.lang.reflect.Method;

import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.lang.Nullable;

/**
* Interface containing methods and value objects to obtain information about the current repository method invocation.
* <p>
* The {@link #currentMethod()} method is usable if the repository factory is configured to expose the current
* repository method metadata (not the default). It returns the invoked repository method. Target objects or advice can
* use this to make advised calls.
* The {@link #getMetadata()} method is usable if the repository factory is configured to expose the current repository
* method metadata (not the default). It returns the invoked repository method. Target objects or advice can use this to
* make advised calls.
* <p>
* Spring Data's framework does not expose method metadata by default, as there is a performance cost in doing so.
* <p>
Expand All @@ -35,7 +32,8 @@
*
* @author Christoph Strobl
* @author Mark Paluch
* @since 3.4.0
* @author Oliver Drotbohm
* @since 3.4
*/
public interface RepositoryMethodContext {

Expand All @@ -49,46 +47,24 @@ public interface RepositoryMethodContext {
* outside a repository method invocation context, or because the repository has not been configured to
* expose its metadata.
*/
static RepositoryMethodContext currentMethod() throws IllegalStateException {

RepositoryMethodContext metadata = DefaultRepositoryMethodContext.getMetadata();
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;
}

/**
* Make the given repository method metadata available via the {@link #currentMethod()} method.
* <p>
* Note that the caller should be careful to keep the old value as appropriate.
*
* @param metadata the metadata to expose (or {@code null} to reset it)
* @return the old metadata, which may be {@code null} if none was bound
* @see #currentMethod()
*/
@Nullable
static RepositoryMethodContext setCurrentMetadata(@Nullable RepositoryMethodContext metadata) {
return DefaultRepositoryMethodContext.setMetadata(metadata);
static RepositoryMethodContext getContext() throws IllegalStateException {
return RepositoryMethodContextHolder.getContext();
}

/**
* Returns the metadata for the repository.
*
* @return the repository metadata.
* @return the repository metadata, will never be {@literal null}.
*/
RepositoryMetadata getRepository();
RepositoryMetadata getMetadata();

/**
* Returns the current method that is being invoked.
* <p>
* The method object represents the method as being invoked on the repository interface. It doesn't match the backing
* repository implementation in case the method invocation is delegated to an implementation method.
*
* @return the current method.
* @return the current method, will never be {@literal null}.
*/
Method getMethod();

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* 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;

import org.springframework.core.NamedThreadLocal;
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
* @author Mark Paluch
* @since 3.4
* @see RepositoryMethodContext
*/
public class RepositoryMethodContextHolder {

/**
* 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) {

RepositoryMethodContext old = currentMethod.get();
if (context != null) {
currentMethod.set(context);
} else {
currentMethod.remove();
}

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 @@ -17,25 +17,19 @@

import java.lang.reflect.Method;

import org.springframework.core.NamedThreadLocal;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.lang.Nullable;
import org.springframework.data.repository.core.RepositoryMethodContext;
import org.springframework.util.Assert;

/**
* Class containing value objects providing information about the current repository method invocation.
*
* @author Christoph Strobl
* @author Mark Paluch
* @since 3.4.0
* @author Oliver Drotbohm
* @since 3.4
*/
class DefaultRepositoryMethodContext implements RepositoryMethodContext {

/**
* 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");
public class DefaultRepositoryMethodContext implements RepositoryMethodContext {

private final RepositoryMetadata repositoryMetadata;
private final Method method;
Expand All @@ -46,32 +40,27 @@ class DefaultRepositoryMethodContext implements RepositoryMethodContext {
this.method = method;
}

@Nullable
static RepositoryMethodContext getMetadata() {
return currentMethod.get();
}

@Nullable
static RepositoryMethodContext setMetadata(@Nullable RepositoryMethodContext metadata) {
/**
* Creates a new {@link RepositoryMethodContext} for the given {@link Method}.
*
* @param method must not be {@literal null}.
* @return will never be {@literal null}.
*/
public static RepositoryMethodContext forMethod(Method method) {

RepositoryMethodContext old = currentMethod.get();
if (metadata != null) {
currentMethod.set(metadata);
} else {
currentMethod.remove();
}
Assert.notNull(method, "Method must not be null!");

return old;
return new DefaultRepositoryMethodContext(AbstractRepositoryMetadata.getMetadata(method.getDeclaringClass()),
method);
}

@Override
public RepositoryMetadata getRepository() {
public RepositoryMetadata getMetadata() {
return repositoryMetadata;
}

@Override
public Method getMethod() {
return method;
}

}
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 @@ -56,6 +56,8 @@
import org.springframework.data.repository.core.NamedQueries;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.RepositoryMethodContext;
import org.springframework.data.repository.core.RepositoryMethodContextHolder;
import org.springframework.data.repository.core.support.RepositoryComposition.RepositoryFragments;
import org.springframework.data.repository.core.support.RepositoryInvocationMulticaster.DefaultRepositoryInvocationMulticaster;
import org.springframework.data.repository.core.support.RepositoryInvocationMulticaster.NoOpRepositoryInvocationMulticaster;
Expand Down Expand Up @@ -772,15 +774,18 @@ public ExposeMetadataInterceptor(RepositoryMetadata repositoryMetadata) {
public Object invoke(MethodInvocation invocation) throws Throwable {

RepositoryMethodContext oldMetadata = null;

try {
oldMetadata = RepositoryMethodContext
.setCurrentMetadata(new DefaultRepositoryMethodContext(repositoryMetadata, invocation.getMethod()));

oldMetadata = RepositoryMethodContextHolder
.setContext(new DefaultRepositoryMethodContext(repositoryMetadata, invocation.getMethod()));

return invocation.proceed();

} finally {
RepositoryMethodContext.setCurrentMetadata(oldMetadata);
RepositoryMethodContextHolder.setContext(oldMetadata);
}
}

}

/**
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 @@ -28,7 +28,6 @@
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;

import org.springframework.aop.framework.Advised;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.beans.factory.ListableBeanFactory;
Expand Down
Loading