Skip to content

Commit 008e1e2

Browse files
mp911dechristophstrobl
authored andcommitted
Allow configuration of QueryEngine for Map repositories.
EnableMapRepositories now accepts a QueryEngineFactory to configure the QueryEngine and allows configuring the QueryCreator. Closes: #576 Original Pull Request: #577
1 parent c355374 commit 008e1e2

10 files changed

+280
-29
lines changed

src/main/java/org/springframework/data/keyvalue/core/PredicateQueryEngine.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
* @author Christoph Strobl
3333
* @since 3.3
3434
*/
35-
class PredicateQueryEngine extends QueryEngine<KeyValueAdapter, Predicate<?>, Comparator<?>> {
35+
public class PredicateQueryEngine extends QueryEngine<KeyValueAdapter, Predicate<?>, Comparator<?>> {
3636

3737
/**
3838
* Creates a new {@link PredicateQueryEngine}.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright 2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.keyvalue.core;
17+
18+
/**
19+
* Interface for {@code QueryEngineFactory} implementations that provide a {@link QueryEngine} object as part of the
20+
* configuration.
21+
* <p>
22+
* The factory is used during configuration to supply the query engine to be used. When configured, a
23+
* {@code QueryEngineFactory} can be instantiated by accepting a {@link SortAccessor} in its constructor. Otherwise,
24+
* implementations are expected to declare a no-args constructor.
25+
*
26+
* @author Mark Paluch
27+
* @since 3.3.1
28+
*/
29+
public interface QueryEngineFactory {
30+
31+
/**
32+
* Factory method for creating a {@link QueryEngine}.
33+
*
34+
* @return the query engine.
35+
*/
36+
QueryEngine<?, ?, ?> create();
37+
}

src/main/java/org/springframework/data/keyvalue/core/SpelQueryEngine.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,8 @@
3434
* @author Christoph Strobl
3535
* @author Oliver Gierke
3636
* @author Mark Paluch
37-
* @param <T>
3837
*/
39-
class SpelQueryEngine extends QueryEngine<KeyValueAdapter, SpelCriteria, Comparator<?>> {
38+
public class SpelQueryEngine extends QueryEngine<KeyValueAdapter, SpelCriteria, Comparator<?>> {
4039

4140
private static final SpelExpressionParser PARSER = new SpelExpressionParser();
4241

src/main/java/org/springframework/data/keyvalue/repository/config/KeyValueRepositoryConfigurationExtension.java

+9-11
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
2626
import org.springframework.beans.factory.support.RootBeanDefinition;
2727
import org.springframework.core.annotation.AnnotationAttributes;
28+
import org.springframework.core.annotation.MergedAnnotation;
2829
import org.springframework.core.type.AnnotationMetadata;
2930
import org.springframework.data.keyvalue.core.mapping.context.KeyValueMappingContext;
3031
import org.springframework.data.keyvalue.repository.KeyValueRepository;
@@ -36,7 +37,6 @@
3637
import org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport;
3738
import org.springframework.data.repository.config.RepositoryConfigurationSource;
3839
import org.springframework.lang.Nullable;
39-
import org.springframework.util.CollectionUtils;
4040

4141
/**
4242
* {@link RepositoryConfigurationExtension} for {@link KeyValueRepository}.
@@ -90,17 +90,15 @@ public void postProcess(BeanDefinitionBuilder builder, AnnotationRepositoryConfi
9090
*/
9191
private static Class<?> getQueryCreatorType(AnnotationRepositoryConfigurationSource config) {
9292

93-
AnnotationMetadata metadata = config.getEnableAnnotationMetadata();
93+
AnnotationMetadata amd = (AnnotationMetadata) config.getSource();
94+
MergedAnnotation<QueryCreatorType> queryCreator = amd.getAnnotations().get(QueryCreatorType.class);
95+
Class<?> queryCreatorType = queryCreator.isPresent() ? queryCreator.getClass("value") : Class.class;
9496

95-
Map<String, Object> queryCreatorAnnotationAttributes = metadata
96-
.getAnnotationAttributes(QueryCreatorType.class.getName());
97-
98-
if (CollectionUtils.isEmpty(queryCreatorAnnotationAttributes)) {
97+
if (queryCreatorType == Class.class) {
9998
return SpelQueryCreator.class;
10099
}
101100

102-
AnnotationAttributes queryCreatorAttributes = new AnnotationAttributes(queryCreatorAnnotationAttributes);
103-
return queryCreatorAttributes.getClass("value");
101+
return queryCreatorType;
104102
}
105103

106104
/**
@@ -132,10 +130,10 @@ public void registerBeansForRoot(BeanDefinitionRegistry registry, RepositoryConf
132130

133131
registerIfNotAlreadyRegistered(() -> {
134132

135-
RootBeanDefinition definitionefinition = new RootBeanDefinition(KeyValueMappingContext.class);
136-
definitionefinition.setSource(configurationSource.getSource());
133+
RootBeanDefinition mappingContext = new RootBeanDefinition(KeyValueMappingContext.class);
134+
mappingContext.setSource(configurationSource.getSource());
137135

138-
return definitionefinition;
136+
return mappingContext;
139137

140138
}, registry, getMappingContextBeanRef(), configurationSource);
141139

src/main/java/org/springframework/data/keyvalue/repository/query/KeyValuePartTreeQuery.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ public KeyValuePartTreeQuery(QueryMethod queryMethod, QueryMethodEvaluationConte
8989
QueryCreatorFactory<AbstractQueryCreator<KeyValueQuery<?>, ?>> queryCreatorFactory) {
9090

9191
Assert.notNull(queryMethod, "Query method must not be null");
92-
Assert.notNull(evaluationContextProvider, "EvaluationContextprovider must not be null");
92+
Assert.notNull(evaluationContextProvider, "EvaluationContextProvider must not be null");
9393
Assert.notNull(keyValueOperations, "KeyValueOperations must not be null");
9494
Assert.notNull(queryCreatorFactory, "QueryCreatorFactory type must not be null");
9595

src/main/java/org/springframework/data/keyvalue/repository/query/PredicateQueryCreator.java

+2
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@
3838
import org.springframework.util.comparator.NullSafeComparator;
3939

4040
/**
41+
* {@link AbstractQueryCreator} to create {@link Predicate}-based {@link KeyValueQuery}s.
42+
*
4143
* @author Christoph Strobl
4244
* @since 3.3
4345
*/

src/main/java/org/springframework/data/keyvalue/repository/query/SpelQueryCreator.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
import org.springframework.util.StringUtils;
3333

3434
/**
35-
* {@link AbstractQueryCreator} to create {@link SpelExpression} based {@link KeyValueQuery}s.
35+
* {@link AbstractQueryCreator} to create {@link SpelExpression}-based {@link KeyValueQuery}s.
3636
*
3737
* @author Christoph Strobl
3838
* @author Oliver Gierke
@@ -180,8 +180,8 @@ protected SpelExpression toPredicateExpression(PartTree tree) {
180180
case NEGATING_SIMPLE_PROPERTY:
181181
case EXISTS:
182182
default:
183-
throw new InvalidDataAccessApiUsageException(String.format("Found invalid part '%s' in query",
184-
part.getType()));
183+
throw new InvalidDataAccessApiUsageException(
184+
String.format("Found invalid part '%s' in query", part.getType()));
185185
}
186186

187187
if (partIter.hasNext()) {

src/main/java/org/springframework/data/map/repository/config/EnableMapRepositories.java

+25-3
Original file line numberDiff line numberDiff line change
@@ -28,30 +28,33 @@
2828
import org.springframework.context.ApplicationContext;
2929
import org.springframework.context.annotation.ComponentScan.Filter;
3030
import org.springframework.context.annotation.Import;
31+
import org.springframework.core.annotation.AliasFor;
3132
import org.springframework.data.keyvalue.core.KeyValueOperations;
3233
import org.springframework.data.keyvalue.core.KeyValueTemplate;
34+
import org.springframework.data.keyvalue.core.QueryEngineFactory;
3335
import org.springframework.data.keyvalue.core.SortAccessor;
3436
import org.springframework.data.keyvalue.repository.config.QueryCreatorType;
35-
import org.springframework.data.keyvalue.repository.query.KeyValuePartTreeQuery;
3637
import org.springframework.data.keyvalue.repository.query.PredicateQueryCreator;
3738
import org.springframework.data.keyvalue.repository.support.KeyValueRepositoryFactoryBean;
3839
import org.springframework.data.repository.config.DefaultRepositoryBaseClass;
3940
import org.springframework.data.repository.query.QueryLookupStrategy;
4041
import org.springframework.data.repository.query.QueryLookupStrategy.Key;
42+
import org.springframework.data.repository.query.parser.AbstractQueryCreator;
4143

4244
/**
4345
* Annotation to activate Map repositories. If no base package is configured through either {@link #value()},
4446
* {@link #basePackages()} or {@link #basePackageClasses()} it will trigger scanning of the package of annotated class.
4547
*
4648
* @author Christoph Strobl
4749
* @author Oliver Gierke
50+
* @author Mark Paluch
4851
*/
4952
@Target(ElementType.TYPE)
5053
@Retention(RetentionPolicy.RUNTIME)
5154
@Documented
5255
@Inherited
5356
@Import(MapRepositoriesRegistrar.class)
54-
@QueryCreatorType(value = PredicateQueryCreator.class, repositoryQueryType = KeyValuePartTreeQuery.class)
57+
@QueryCreatorType(PredicateQueryCreator.class)
5558
public @interface EnableMapRepositories {
5659

5760
/**
@@ -145,10 +148,29 @@
145148
@SuppressWarnings("rawtypes")
146149
Class<? extends Map> mapType() default ConcurrentHashMap.class;
147150

151+
/**
152+
* Configures the {@link QueryEngineFactory} to create the QueryEngine. When both, the query engine and sort accessors
153+
* are configured, the query engine is instantiated using the configured sort accessor.
154+
*
155+
* @return {@link QueryEngineFactory} to configure the QueryEngine.
156+
* @since 3.3.1
157+
*/
158+
Class<? extends QueryEngineFactory> queryEngineFactory() default QueryEngineFactory.class;
159+
160+
/**
161+
* Configures the {@code QueryCreator} to create Part-Tree queries. The QueryCreator must create queries supported by
162+
* the underlying {@code QueryEngine}.
163+
*
164+
* @return {@link AbstractQueryCreator}
165+
* @since 3.3.1
166+
*/
167+
@AliasFor(annotation = QueryCreatorType.class, value = "value")
168+
Class<? extends AbstractQueryCreator<?, ?>> queryCreator() default PredicateQueryCreator.class;
169+
148170
/**
149171
* Configures the {@link SortAccessor accessor} for sorting results.
150172
*
151-
* @return {@link SortAccessor} to indicate usage of default implementation.
173+
* @return the configured {@link SortAccessor}.
152174
* @since 3.1.10
153175
*/
154176
Class<? extends SortAccessor> sortAccessor() default SortAccessor.class;

src/main/java/org/springframework/data/map/repository/config/MapRepositoryConfigurationExtension.java

+59-6
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package org.springframework.data.map.repository.config;
1717

18+
import java.lang.reflect.Constructor;
1819
import java.util.Map;
1920

2021
import org.springframework.beans.BeanUtils;
@@ -24,15 +25,23 @@
2425
import org.springframework.core.type.AnnotationMetadata;
2526
import org.springframework.data.config.ParsingUtils;
2627
import org.springframework.data.keyvalue.core.KeyValueTemplate;
28+
import org.springframework.data.keyvalue.core.QueryEngine;
29+
import org.springframework.data.keyvalue.core.QueryEngineFactory;
2730
import org.springframework.data.keyvalue.core.SortAccessor;
2831
import org.springframework.data.keyvalue.repository.config.KeyValueRepositoryConfigurationExtension;
2932
import org.springframework.data.map.MapKeyValueAdapter;
33+
import org.springframework.data.repository.config.RepositoryConfigurationExtension;
3034
import org.springframework.data.repository.config.RepositoryConfigurationSource;
3135
import org.springframework.lang.Nullable;
36+
import org.springframework.util.ClassUtils;
3237

3338
/**
39+
* {@link RepositoryConfigurationExtension} for Map-based repositories.
40+
*
3441
* @author Christoph Strobl
42+
* @author Mark Paluch
3543
*/
44+
@SuppressWarnings("unchecked")
3645
public class MapRepositoryConfigurationExtension extends KeyValueRepositoryConfigurationExtension {
3746

3847
@Override
@@ -58,7 +67,11 @@ protected AbstractBeanDefinition getDefaultKeyValueTemplateBeanDefinition(
5867
adapterBuilder.addConstructorArgValue(getMapTypeToUse(configurationSource));
5968

6069
SortAccessor<?> sortAccessor = getSortAccessor(configurationSource);
61-
if(sortAccessor != null) {
70+
QueryEngine<?, ?, ?> queryEngine = getQueryEngine(sortAccessor, configurationSource);
71+
72+
if (queryEngine != null) {
73+
adapterBuilder.addConstructorArgValue(queryEngine);
74+
} else if (sortAccessor != null) {
6275
adapterBuilder.addConstructorArgValue(sortAccessor);
6376
}
6477

@@ -73,20 +86,60 @@ protected AbstractBeanDefinition getDefaultKeyValueTemplateBeanDefinition(
7386
@SuppressWarnings({ "unchecked", "rawtypes" })
7487
private static Class<? extends Map> getMapTypeToUse(RepositoryConfigurationSource source) {
7588

76-
return (Class<? extends Map>) ((AnnotationMetadata) source.getSource()).getAnnotationAttributes(
77-
EnableMapRepositories.class.getName()).get("mapType");
89+
return (Class<? extends Map>) getAnnotationAttributes(source).get("mapType");
7890
}
7991

8092
@Nullable
8193
private static SortAccessor<?> getSortAccessor(RepositoryConfigurationSource source) {
8294

83-
Class<? extends SortAccessor<?>> sortAccessorType = (Class<? extends SortAccessor<?>>) ((AnnotationMetadata) source.getSource()).getAnnotationAttributes(
84-
EnableMapRepositories.class.getName()).get("sortAccessor");
95+
Class<? extends SortAccessor<?>> sortAccessorType = (Class<? extends SortAccessor<?>>) getAnnotationAttributes(
96+
source).get("sortAccessor");
8597

86-
if(sortAccessorType != null && !sortAccessorType.isInterface()) {
98+
if (sortAccessorType != null && !sortAccessorType.isInterface()) {
8799
return BeanUtils.instantiateClass(sortAccessorType);
88100
}
89101

90102
return null;
91103
}
104+
105+
@Nullable
106+
private static QueryEngine<?, ?, ?> getQueryEngine(@Nullable SortAccessor<?> sortAccessor,
107+
RepositoryConfigurationSource source) {
108+
109+
Class<? extends QueryEngineFactory> queryEngineFactoryType = (Class<? extends QueryEngineFactory>) getAnnotationAttributes(
110+
source).get("queryEngineFactory");
111+
112+
if (queryEngineFactoryType != null && !queryEngineFactoryType.isInterface()) {
113+
114+
if (sortAccessor != null) {
115+
Constructor<? extends QueryEngineFactory> constructor = ClassUtils
116+
.getConstructorIfAvailable(queryEngineFactoryType, SortAccessor.class);
117+
if (constructor != null) {
118+
return BeanUtils.instantiateClass(constructor, sortAccessor).create();
119+
}
120+
}
121+
122+
return BeanUtils.instantiateClass(queryEngineFactoryType).create();
123+
}
124+
125+
return null;
126+
}
127+
128+
private static Map<String, Object> getAnnotationAttributes(RepositoryConfigurationSource source) {
129+
130+
AnnotationMetadata annotationSource = (AnnotationMetadata) source.getSource();
131+
132+
if (annotationSource == null) {
133+
throw new IllegalArgumentException("AnnotationSource not available");
134+
}
135+
136+
Map<String, Object> annotationAttributes = annotationSource
137+
.getAnnotationAttributes(EnableMapRepositories.class.getName());
138+
139+
if (annotationAttributes == null) {
140+
throw new IllegalStateException("No annotation attributes for @EnableMapRepositories");
141+
}
142+
143+
return annotationAttributes;
144+
}
92145
}

0 commit comments

Comments
 (0)