Skip to content

Commit 272f68b

Browse files
mp911demarcingrzejszczak
authored andcommitted
Add ValueExpression infrastructure for query methods.
Introduce ValueExpressionQueryRewriter as replacement for SpelQueryContext.
1 parent a7b5b48 commit 272f68b

10 files changed

+920
-31
lines changed

src/main/java/org/springframework/data/repository/core/support/RepositoryFactoryBeanSupport.java

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
import org.springframework.beans.factory.ListableBeanFactory;
2929
import org.springframework.context.ApplicationEventPublisher;
3030
import org.springframework.context.ApplicationEventPublisherAware;
31+
import org.springframework.context.EnvironmentAware;
32+
import org.springframework.core.env.Environment;
3133
import org.springframework.data.mapping.PersistentEntity;
3234
import org.springframework.data.mapping.context.MappingContext;
3335
import org.springframework.data.repository.Repository;
@@ -63,8 +65,8 @@
6365
* @author Johannes Englmeier
6466
*/
6567
public abstract class RepositoryFactoryBeanSupport<T extends Repository<S, ID>, S, ID>
66-
implements InitializingBean, RepositoryFactoryInformation<S, ID>, FactoryBean<T>, BeanClassLoaderAware,
67-
BeanFactoryAware, ApplicationEventPublisherAware {
68+
implements InitializingBean, RepositoryFactoryInformation<S, ID>, FactoryBean<T>, ApplicationEventPublisherAware,
69+
BeanClassLoaderAware, BeanFactoryAware, EnvironmentAware {
6870

6971
private final Class<? extends T> repositoryInterface;
7072

@@ -74,14 +76,15 @@ public abstract class RepositoryFactoryBeanSupport<T extends Repository<S, ID>,
7476
private Optional<Class<?>> repositoryBaseClass = Optional.empty();
7577
private Optional<Object> customImplementation = Optional.empty();
7678
private Optional<RepositoryFragments> repositoryFragments = Optional.empty();
77-
private NamedQueries namedQueries;
79+
private NamedQueries namedQueries = PropertiesBasedNamedQueries.EMPTY;
7880
private Optional<MappingContext<?, ?>> mappingContext = Optional.empty();
7981
private ClassLoader classLoader;
82+
private ApplicationEventPublisher publisher;
8083
private BeanFactory beanFactory;
84+
private Environment environment;
8185
private boolean lazyInit = false;
8286
private Optional<QueryMethodEvaluationContextProvider> evaluationContextProvider = Optional.empty();
83-
private List<RepositoryFactoryCustomizer> repositoryFactoryCustomizers = new ArrayList<>();
84-
private ApplicationEventPublisher publisher;
87+
private final List<RepositoryFactoryCustomizer> repositoryFactoryCustomizers = new ArrayList<>();
8588

8689
private Lazy<T> repository;
8790

@@ -216,6 +219,11 @@ public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
216219
}
217220
}
218221

222+
@Override
223+
public void setEnvironment(Environment environment) {
224+
this.environment = environment;
225+
}
226+
219227
/**
220228
* Create a default {@link QueryMethodEvaluationContextProvider} (or subclass) from {@link ListableBeanFactory}.
221229
*
@@ -233,11 +241,13 @@ public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
233241
this.publisher = publisher;
234242
}
235243

244+
@Override
236245
@SuppressWarnings("unchecked")
237246
public EntityInformation<S, ID> getEntityInformation() {
238247
return (EntityInformation<S, ID>) factory.getEntityInformation(repositoryMetadata.getDomainType());
239248
}
240249

250+
@Override
241251
public RepositoryInformation getRepositoryInformation() {
242252

243253
RepositoryFragments fragments = customImplementation.map(RepositoryFragments::just)//
@@ -246,30 +256,36 @@ public RepositoryInformation getRepositoryInformation() {
246256
return factory.getRepositoryInformation(repositoryMetadata, fragments);
247257
}
248258

259+
@Override
249260
public PersistentEntity<?, ?> getPersistentEntity() {
250261

251262
return mappingContext.orElseThrow(() -> new IllegalStateException("No MappingContext available"))
252263
.getRequiredPersistentEntity(repositoryMetadata.getDomainType());
253264
}
254265

266+
@Override
255267
public List<QueryMethod> getQueryMethods() {
256268
return factory.getQueryMethods();
257269
}
258270

271+
@Override
259272
@NonNull
260273
public T getObject() {
261274
return this.repository.get();
262275
}
263276

277+
@Override
264278
@NonNull
265279
public Class<? extends T> getObjectType() {
266280
return repositoryInterface;
267281
}
268282

283+
@Override
269284
public boolean isSingleton() {
270285
return true;
271286
}
272287

288+
@Override
273289
public void afterPropertiesSet() {
274290

275291
this.factory = createRepositoryFactory();
@@ -281,10 +297,14 @@ public void afterPropertiesSet() {
281297
this.factory.setBeanClassLoader(classLoader);
282298
this.factory.setBeanFactory(beanFactory);
283299

284-
if (publisher != null) {
300+
if (this.publisher != null) {
285301
this.factory.addRepositoryProxyPostProcessor(new EventPublishingRepositoryProxyPostProcessor(publisher));
286302
}
287303

304+
if (this.environment != null) {
305+
this.factory.setEnvironment(this.environment);
306+
}
307+
288308
repositoryBaseClass.ifPresent(this.factory::setRepositoryBaseClass);
289309

290310
this.repositoryFactoryCustomizers.forEach(customizer -> customizer.customize(this.factory));

src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java

Lines changed: 76 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,16 @@
3838
import org.springframework.beans.factory.BeanFactory;
3939
import org.springframework.beans.factory.BeanFactoryAware;
4040
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
41+
import org.springframework.context.EnvironmentAware;
4142
import org.springframework.core.convert.support.DefaultConversionService;
4243
import org.springframework.core.convert.support.GenericConversionService;
44+
import org.springframework.core.env.Environment;
45+
import org.springframework.core.env.EnvironmentCapable;
46+
import org.springframework.core.env.StandardEnvironment;
4347
import org.springframework.core.log.LogMessage;
4448
import org.springframework.core.metrics.ApplicationStartup;
4549
import org.springframework.core.metrics.StartupStep;
50+
import org.springframework.data.expression.ValueExpressionParser;
4651
import org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor;
4752
import org.springframework.data.projection.ProjectionFactory;
4853
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
@@ -58,10 +63,14 @@
5863
import org.springframework.data.repository.query.QueryLookupStrategy.Key;
5964
import org.springframework.data.repository.query.QueryMethod;
6065
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
66+
import org.springframework.data.repository.query.QueryMethodValueEvaluationContextProviderFactory;
6167
import org.springframework.data.repository.query.RepositoryQuery;
68+
import org.springframework.data.repository.query.ValueExpressionSupportHolder;
6269
import org.springframework.data.repository.util.QueryExecutionConverters;
6370
import org.springframework.data.util.Lazy;
6471
import org.springframework.data.util.ReflectionUtils;
72+
import org.springframework.expression.ExpressionParser;
73+
import org.springframework.expression.spel.standard.SpelExpressionParser;
6574
import org.springframework.lang.Nullable;
6675
import org.springframework.transaction.interceptor.TransactionalProxy;
6776
import org.springframework.util.Assert;
@@ -80,9 +89,13 @@
8089
* @author John Blum
8190
* @author Johannes Englmeier
8291
*/
83-
public abstract class RepositoryFactorySupport implements BeanClassLoaderAware, BeanFactoryAware {
92+
public abstract class RepositoryFactorySupport
93+
implements BeanClassLoaderAware, BeanFactoryAware, EnvironmentAware, EnvironmentCapable {
8494

8595
static final GenericConversionService CONVERSION_SERVICE = new DefaultConversionService();
96+
private static final ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();
97+
private static final ValueExpressionParser VALUE_PARSER = ValueExpressionParser.create(() -> EXPRESSION_PARSER);
98+
8699
private static final Log logger = LogFactory.getLog(RepositoryFactorySupport.class);
87100

88101
static {
@@ -93,15 +106,16 @@ public abstract class RepositoryFactorySupport implements BeanClassLoaderAware,
93106
private final Map<RepositoryInformationCacheKey, RepositoryInformation> repositoryInformationCache;
94107
private final List<RepositoryProxyPostProcessor> postProcessors;
95108

96-
private Optional<Class<?>> repositoryBaseClass;
109+
private @Nullable Class<?> repositoryBaseClass;
97110
private boolean exposeMetadata;
98111
private @Nullable QueryLookupStrategy.Key queryLookupStrategyKey;
99-
private List<QueryCreationListener<?>> queryPostProcessors;
100-
private List<RepositoryMethodInvocationListener> methodInvocationListeners;
112+
private final List<QueryCreationListener<?>> queryPostProcessors;
113+
private final List<RepositoryMethodInvocationListener> methodInvocationListeners;
101114
private NamedQueries namedQueries;
102115
private ClassLoader classLoader;
103116
private QueryMethodEvaluationContextProvider evaluationContextProvider;
104117
private BeanFactory beanFactory;
118+
private Environment environment;
105119
private Lazy<ProjectionFactory> projectionFactory;
106120

107121
private final QueryCollectingQueryCreationListener collectingListener = new QueryCollectingQueryCreationListener();
@@ -112,7 +126,6 @@ public RepositoryFactorySupport() {
112126
this.repositoryInformationCache = new HashMap<>(16);
113127
this.postProcessors = new ArrayList<>();
114128

115-
this.repositoryBaseClass = Optional.empty();
116129
this.namedQueries = PropertiesBasedNamedQueries.EMPTY;
117130
this.classLoader = org.springframework.util.ClassUtils.getDefaultClassLoader();
118131
this.evaluationContextProvider = QueryMethodEvaluationContextProvider.DEFAULT;
@@ -143,7 +156,7 @@ public void setExposeMetadata(boolean exposeMetadata) {
143156
}
144157

145158
/**
146-
* Sets the strategy of how to lookup a query to execute finders.
159+
* Sets the strategy of how to look up a query to execute finders.
147160
*
148161
* @param key
149162
*/
@@ -156,12 +169,12 @@ public void setQueryLookupStrategyKey(Key key) {
156169
*
157170
* @param namedQueries the namedQueries to set
158171
*/
159-
public void setNamedQueries(NamedQueries namedQueries) {
172+
public void setNamedQueries(@Nullable NamedQueries namedQueries) {
160173
this.namedQueries = namedQueries == null ? PropertiesBasedNamedQueries.EMPTY : namedQueries;
161174
}
162175

163176
@Override
164-
public void setBeanClassLoader(ClassLoader classLoader) {
177+
public void setBeanClassLoader(@Nullable ClassLoader classLoader) {
165178
this.classLoader = classLoader == null ? org.springframework.util.ClassUtils.getDefaultClassLoader() : classLoader;
166179
this.projectionFactory = createProjectionFactory();
167180
}
@@ -172,14 +185,29 @@ public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
172185
this.projectionFactory = createProjectionFactory();
173186
}
174187

188+
@Override
189+
public void setEnvironment(Environment environment) {
190+
this.environment = environment;
191+
}
192+
193+
@Override
194+
public Environment getEnvironment() {
195+
196+
if (this.environment == null) {
197+
this.environment = new StandardEnvironment();
198+
}
199+
200+
return this.environment;
201+
}
202+
175203
/**
176204
* Sets the {@link QueryMethodEvaluationContextProvider} to be used to evaluate SpEL expressions in manually defined
177205
* queries.
178206
*
179207
* @param evaluationContextProvider can be {@literal null}, defaults to
180208
* {@link QueryMethodEvaluationContextProvider#DEFAULT}.
181209
*/
182-
public void setEvaluationContextProvider(QueryMethodEvaluationContextProvider evaluationContextProvider) {
210+
public void setEvaluationContextProvider(@Nullable QueryMethodEvaluationContextProvider evaluationContextProvider) {
183211
this.evaluationContextProvider = evaluationContextProvider == null ? QueryMethodEvaluationContextProvider.DEFAULT
184212
: evaluationContextProvider;
185213
}
@@ -191,15 +219,15 @@ public void setEvaluationContextProvider(QueryMethodEvaluationContextProvider ev
191219
* @param repositoryBaseClass the repository base class to back the repository proxy, can be {@literal null}.
192220
* @since 1.11
193221
*/
194-
public void setRepositoryBaseClass(Class<?> repositoryBaseClass) {
195-
this.repositoryBaseClass = Optional.ofNullable(repositoryBaseClass);
222+
public void setRepositoryBaseClass(@Nullable Class<?> repositoryBaseClass) {
223+
this.repositoryBaseClass = repositoryBaseClass;
196224
}
197225

198226
/**
199227
* Adds a {@link QueryCreationListener} to the factory to plug in functionality triggered right after creation of
200228
* {@link RepositoryQuery} instances.
201229
*
202-
* @param listener
230+
* @param listener the listener to add.
203231
*/
204232
public void addQueryCreationListener(QueryCreationListener<?> listener) {
205233

@@ -211,7 +239,7 @@ public void addQueryCreationListener(QueryCreationListener<?> listener) {
211239
* Adds a {@link RepositoryMethodInvocationListener} to the factory to plug in functionality triggered right after
212240
* running {@link RepositoryQuery query methods} and {@link Method fragment methods}.
213241
*
214-
* @param listener
242+
* @param listener the listener to add.
215243
* @since 2.4
216244
*/
217245
public void addInvocationListener(RepositoryMethodInvocationListener listener) {
@@ -225,7 +253,7 @@ public void addInvocationListener(RepositoryMethodInvocationListener listener) {
225253
* the proxy gets created. Note that the {@link QueryExecutorMethodInterceptor} will be added to the proxy
226254
* <em>after</em> the {@link RepositoryProxyPostProcessor}s are considered.
227255
*
228-
* @param processor
256+
* @param processor the post-processor to add.
229257
*/
230258
public void addRepositoryProxyPostProcessor(RepositoryProxyPostProcessor processor) {
231259

@@ -236,8 +264,8 @@ public void addRepositoryProxyPostProcessor(RepositoryProxyPostProcessor process
236264
/**
237265
* Creates {@link RepositoryFragments} based on {@link RepositoryMetadata} to add repository-specific extensions.
238266
*
239-
* @param metadata
240-
* @return
267+
* @param metadata the repository metadata to use.
268+
* @return fragment composition.
241269
*/
242270
protected RepositoryFragments getRepositoryFragments(RepositoryMetadata metadata) {
243271
return RepositoryFragments.empty();
@@ -246,8 +274,8 @@ protected RepositoryFragments getRepositoryFragments(RepositoryMetadata metadata
246274
/**
247275
* Creates {@link RepositoryComposition} based on {@link RepositoryMetadata} for repository-specific method handling.
248276
*
249-
* @param metadata
250-
* @return
277+
* @param metadata the repository metadata to use.
278+
* @return repository composition.
251279
*/
252280
private RepositoryComposition getRepositoryComposition(RepositoryMetadata metadata) {
253281
return RepositoryComposition.fromMetadata(metadata);
@@ -257,7 +285,7 @@ private RepositoryComposition getRepositoryComposition(RepositoryMetadata metada
257285
* Returns a repository instance for the given interface.
258286
*
259287
* @param repositoryInterface must not be {@literal null}.
260-
* @return
288+
* @return the implemented repository interface.
261289
*/
262290
public <T> T getRepository(Class<T> repositoryInterface) {
263291
return getRepository(repositoryInterface, RepositoryFragments.empty());
@@ -269,7 +297,7 @@ public <T> T getRepository(Class<T> repositoryInterface) {
269297
*
270298
* @param repositoryInterface must not be {@literal null}.
271299
* @param customImplementation must not be {@literal null}.
272-
* @return
300+
* @return the implemented repository interface.
273301
*/
274302
public <T> T getRepository(Class<T> repositoryInterface, Object customImplementation) {
275303
return getRepository(repositoryInterface, RepositoryFragments.just(customImplementation));
@@ -281,7 +309,7 @@ public <T> T getRepository(Class<T> repositoryInterface, Object customImplementa
281309
*
282310
* @param repositoryInterface must not be {@literal null}.
283311
* @param fragments must not be {@literal null}.
284-
* @return
312+
* @return the implemented repository interface.
285313
* @since 2.0
286314
*/
287315
@SuppressWarnings({ "unchecked" })
@@ -298,7 +326,9 @@ public <T> T getRepository(Class<T> repositoryInterface, RepositoryFragments fra
298326

299327
StartupStep repositoryInit = onEvent(applicationStartup, "spring.data.repository.init", repositoryInterface);
300328

301-
repositoryBaseClass.ifPresent(it -> repositoryInit.tag("baseClass", it.getName()));
329+
if (repositoryBaseClass != null) {
330+
repositoryInit.tag("baseClass", repositoryBaseClass.getName());
331+
}
302332

303333
StartupStep repositoryMetadataStep = onEvent(applicationStartup, "spring.data.repository.metadata",
304334
repositoryInterface);
@@ -384,7 +414,9 @@ public <T> T getRepository(Class<T> repositoryInterface, RepositoryFragments fra
384414
}
385415

386416
Optional<QueryLookupStrategy> queryLookupStrategy = getQueryLookupStrategy(queryLookupStrategyKey,
387-
evaluationContextProvider);
417+
new ValueExpressionSupportHolder(
418+
new QueryMethodValueEvaluationContextProviderFactory(getEnvironment(), evaluationContextProvider),
419+
VALUE_PARSER));
388420
result.addAdvice(new QueryExecutorMethodInterceptor(information, getProjectionFactory(), queryLookupStrategy,
389421
namedQueries, queryPostProcessors, methodInvocationListeners));
390422

@@ -412,7 +444,7 @@ public <T> T getRepository(Class<T> repositoryInterface, RepositoryFragments fra
412444
*/
413445
protected ProjectionFactory getProjectionFactory(ClassLoader classLoader, BeanFactory beanFactory) {
414446

415-
SpelAwareProxyProjectionFactory factory = new SpelAwareProxyProjectionFactory();
447+
SpelAwareProxyProjectionFactory factory = new SpelAwareProxyProjectionFactory(EXPRESSION_PARSER);
416448
factory.setBeanClassLoader(classLoader);
417449
factory.setBeanFactory(beanFactory);
418450

@@ -476,7 +508,7 @@ private RepositoryInformation getRepositoryInformation(RepositoryMetadata metada
476508

477509
return repositoryInformationCache.computeIfAbsent(cacheKey, key -> {
478510

479-
Class<?> baseClass = repositoryBaseClass.orElse(getRepositoryBaseClass(metadata));
511+
Class<?> baseClass = repositoryBaseClass != null ? repositoryBaseClass : getRepositoryBaseClass(metadata);
480512

481513
return new DefaultRepositoryInformation(metadata, baseClass, composition);
482514
});
@@ -537,6 +569,25 @@ protected Optional<QueryLookupStrategy> getQueryLookupStrategy(@Nullable Key key
537569
return Optional.empty();
538570
}
539571

572+
/**
573+
* Returns the {@link QueryLookupStrategy} for the given {@link Key} and {@link ValueExpressionSupportHolder}. Favor
574+
* implementing this method over {@link #getQueryLookupStrategy(Key, QueryMethodEvaluationContextProvider)} for
575+
* extended {@link org.springframework.data.expression.ValueExpression} support.
576+
* <p>
577+
* This method delegates to {@link #getQueryLookupStrategy(Key, QueryMethodEvaluationContextProvider)} unless
578+
* overridden.
579+
* </p>
580+
*
581+
* @param key can be {@literal null}.
582+
* @param expressionSupport will never be {@literal null}.
583+
* @return the {@link QueryLookupStrategy} to use or {@literal null} if no queries should be looked up.
584+
* @since 3.3
585+
*/
586+
protected Optional<QueryLookupStrategy> getQueryLookupStrategy(@Nullable Key key,
587+
ValueExpressionSupportHolder expressionSupport) {
588+
return getQueryLookupStrategy(key, evaluationContextProvider);
589+
}
590+
540591
/**
541592
* Validates the given repository interface as well as the given custom implementation.
542593
*

0 commit comments

Comments
 (0)