Skip to content

Commit 2aeaa70

Browse files
committed
Polishing.
Add configuration for constructor origin. See #3344 Original pull request: #3351
1 parent 09a8d9d commit 2aeaa70

File tree

9 files changed

+483
-84
lines changed

9 files changed

+483
-84
lines changed

src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryBeanDefinitionPropertiesDecorator.java

Lines changed: 41 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,22 +15,28 @@
1515
*/
1616
package org.springframework.data.repository.aot.generate;
1717

18+
import java.util.ArrayList;
1819
import java.util.LinkedHashMap;
20+
import java.util.List;
1921
import java.util.Map;
2022
import java.util.Map.Entry;
2123
import java.util.function.Supplier;
2224

2325
import javax.lang.model.element.Modifier;
2426

2527
import org.springframework.beans.factory.BeanFactory;
28+
import org.springframework.beans.factory.config.BeanDefinition;
29+
import org.springframework.core.DefaultParameterNameDiscoverer;
30+
import org.springframework.core.MethodParameter;
2631
import org.springframework.core.ResolvableType;
32+
import org.springframework.data.repository.aot.generate.AotRepositoryFragmentMetadata.ConstructorArgument;
2733
import org.springframework.data.repository.core.support.RepositoryComposition;
2834
import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
2935
import org.springframework.javapoet.CodeBlock;
3036
import org.springframework.javapoet.MethodSpec;
31-
import org.springframework.javapoet.TypeName;
3237
import org.springframework.javapoet.TypeSpec;
3338
import org.springframework.util.Assert;
39+
import org.springframework.util.ReflectionUtils;
3440
import org.springframework.util.StringUtils;
3541

3642
/**
@@ -44,14 +50,15 @@
4450
*/
4551
public class AotRepositoryBeanDefinitionPropertiesDecorator {
4652

47-
private static final Map<ResolvableType, String> RESERVED_TYPES;
53+
static final Map<ResolvableType, String> RESERVED_TYPES;
4854

4955
private final Supplier<CodeBlock> inheritedProperties;
5056
private final RepositoryContributor repositoryContributor;
5157

5258
static {
5359

54-
RESERVED_TYPES = new LinkedHashMap<>(2);
60+
RESERVED_TYPES = new LinkedHashMap<>(3);
61+
RESERVED_TYPES.put(ResolvableType.forClass(BeanDefinition.class), "beanDefinition");
5562
RESERVED_TYPES.put(ResolvableType.forClass(BeanFactory.class), "beanFactory");
5663
RESERVED_TYPES.put(ResolvableType.forClass(RepositoryFactoryBeanSupport.FragmentCreationContext.class), "context");
5764
}
@@ -90,9 +97,17 @@ public CodeBlock decorate() {
9097
MethodSpec.Builder callbackMethod = MethodSpec.methodBuilder("getRepositoryFragments").addModifiers(Modifier.PUBLIC)
9198
.returns(RepositoryComposition.RepositoryFragments.class);
9299

93-
for (Entry<ResolvableType, String> entry : RESERVED_TYPES.entrySet()) {
94-
callbackMethod.addParameter(entry.getKey().toClass(), entry.getValue());
95-
}
100+
ReflectionUtils.doWithMethods(RepositoryFactoryBeanSupport.RepositoryFragmentsFunction.class, it -> {
101+
102+
for (int i = 0; i < it.getParameterCount(); i++) {
103+
104+
MethodParameter parameter = new MethodParameter(it, i);
105+
parameter.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());
106+
107+
callbackMethod.addParameter(parameter.getParameterType(), parameter.getParameterName());
108+
}
109+
110+
}, method -> method.getName().equals("getRepositoryFragments"));
96111

97112
callbackMethod.addCode(buildCallbackBody());
98113

@@ -109,26 +124,33 @@ public CodeBlock decorate() {
109124
private CodeBlock buildCallbackBody() {
110125

111126
CodeBlock.Builder callback = CodeBlock.builder();
127+
List<Object> arguments = new ArrayList<>();
112128

113-
for (Entry<String, ResolvableType> entry : repositoryContributor.requiredArgs().entrySet()) {
129+
for (Entry<String, ConstructorArgument> entry : repositoryContributor.getConstructorArguments().entrySet()) {
114130

115-
TypeName argumentType = TypeName.get(entry.getValue().getType());
116-
String reservedArgumentName = RESERVED_TYPES.get(entry.getValue());
117-
if (reservedArgumentName == null) {
118-
callback.addStatement("$1T $2L = beanFactory.getBean($1T.class)", argumentType, entry.getKey());
119-
} else {
131+
ConstructorArgument argument = entry.getValue();
132+
AotRepositoryConstructorBuilder.ParameterOrigin parameterOrigin = argument.parameterOrigin();
120133

121-
if (reservedArgumentName.equals(entry.getKey())) {
122-
continue;
123-
}
134+
String ref = parameterOrigin.getReference();
135+
CodeBlock codeBlock = parameterOrigin.getCodeBlock();
124136

125-
callback.addStatement("$T $L = $L", argumentType, entry.getKey(), reservedArgumentName);
137+
if (StringUtils.hasText(ref)) {
138+
arguments.add(ref);
139+
if (!codeBlock.isEmpty()) {
140+
callback.add(codeBlock);
141+
}
142+
} else {
143+
arguments.add(codeBlock);
126144
}
127145
}
128146

129-
callback.addStatement("return $T.just(new $L($L))", RepositoryComposition.RepositoryFragments.class,
130-
repositoryContributor.getContributedTypeName().getCanonicalName(),
131-
StringUtils.collectionToDelimitedString(repositoryContributor.requiredArgs().keySet(), ", "));
147+
List<Object> args = new ArrayList<>();
148+
args.add(RepositoryComposition.RepositoryFragments.class);
149+
args.add(repositoryContributor.getContributedTypeName().getCanonicalName());
150+
args.addAll(arguments);
151+
152+
callback.addStatement("return $T.just(new $L(%s%s))".formatted("$L".repeat(arguments.isEmpty() ? 0 : 1),
153+
", $L".repeat(Math.max(0, arguments.size() - 1))), args.toArray());
132154

133155
return callback.build();
134156
}

src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryConstructorBuilder.java

Lines changed: 175 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,15 @@
1515
*/
1616
package org.springframework.data.repository.aot.generate;
1717

18+
import java.util.function.Consumer;
19+
import java.util.function.Function;
20+
21+
import org.jspecify.annotations.Nullable;
22+
23+
import org.springframework.beans.factory.config.BeanReference;
1824
import org.springframework.core.ResolvableType;
1925
import org.springframework.javapoet.CodeBlock;
26+
import org.springframework.util.Assert;
2027

2128
/**
2229
* Builder for AOT Repository Constructors.
@@ -65,7 +72,31 @@ default void addParameter(String parameterName, Class<?> type, boolean bindToFie
6572
* @param type parameter type.
6673
* @param bindToField whether to create a field for the parameter and assign its value to the field.
6774
*/
68-
void addParameter(String parameterName, ResolvableType type, boolean bindToField);
75+
default void addParameter(String parameterName, ResolvableType type, boolean bindToField) {
76+
addParameter(parameterName, type, c -> c.bindToField(bindToField));
77+
}
78+
79+
/**
80+
* Add constructor parameter.
81+
*
82+
* @param parameterName name of the parameter.
83+
* @param type parameter type.
84+
* @param parameterCustomizer customizer for the parameter.
85+
*/
86+
default void addParameter(String parameterName, Class<?> type,
87+
Consumer<ConstructorParameterCustomizer> parameterCustomizer) {
88+
addParameter(parameterName, ResolvableType.forClass(type), parameterCustomizer);
89+
}
90+
91+
/**
92+
* Add constructor parameter.
93+
*
94+
* @param parameterName name of the parameter.
95+
* @param type parameter type.
96+
* @param parameterCustomizer customizer for the parameter.
97+
*/
98+
void addParameter(String parameterName, ResolvableType type,
99+
Consumer<ConstructorParameterCustomizer> parameterCustomizer);
69100

70101
/**
71102
* Add constructor body customizer. The customizer is invoked after adding constructor arguments and before assigning
@@ -89,4 +120,147 @@ interface ConstructorCustomizer {
89120

90121
}
91122

123+
/**
124+
* Customizer for a AOT repository constructor parameter.
125+
*/
126+
interface ConstructorParameterCustomizer {
127+
128+
/**
129+
* Bind the constructor parameter to a field of the same type using the original parameter name.
130+
*
131+
* @return {@code this} for method chaining.
132+
*/
133+
default ConstructorParameterCustomizer bindToField() {
134+
return bindToField(true);
135+
}
136+
137+
/**
138+
* Bind the constructor parameter to a field of the same type using the original parameter name.
139+
*
140+
* @return {@code this} for method chaining.
141+
*/
142+
ConstructorParameterCustomizer bindToField(boolean bindToField);
143+
144+
/**
145+
* Use the given {@link BeanReference} to define the constructor parameter origin. Bean references can be by name,
146+
* by type or by type and name. Using a bean reference renders a lookup to a local variable using the parameter name
147+
* as guidance for the local variable name
148+
*
149+
* @see FragmentParameterContext#localVariable(String)
150+
*/
151+
void origin(BeanReference reference);
152+
153+
/**
154+
* Use the given {@link BeanReference} to define the constructor parameter origin. Bean references can be by name,
155+
* by type or by type and name.
156+
*/
157+
void origin(Function<FragmentParameterContext, ParameterOrigin> originFunction);
158+
159+
}
160+
161+
/**
162+
* Context to obtain a constructor parameter value when declaring the constructor parameter origin.
163+
*/
164+
interface FragmentParameterContext {
165+
166+
/**
167+
* @return variable name of the {@link org.springframework.beans.factory.BeanFactory}.
168+
*/
169+
String beanFactory();
170+
171+
/**
172+
* @return parameter origin to obtain the {@link org.springframework.beans.factory.BeanFactory}.
173+
*/
174+
default ParameterOrigin getBeanFactory() {
175+
return ParameterOrigin.ofReference(beanFactory());
176+
}
177+
178+
/**
179+
* @return variable name of the
180+
* {@link org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.FragmentCreationContext}.
181+
*/
182+
String fragmentCreationContext();
183+
184+
/**
185+
* @return parameter origin to obtain the fragment creation context.
186+
*/
187+
default ParameterOrigin getFragmentCreationContext() {
188+
return ParameterOrigin.ofReference(fragmentCreationContext());
189+
}
190+
191+
/**
192+
* Obtain a naming-clash free variant for the given logical variable name within the local method context. Returns
193+
* the target variable name when called multiple times with the same {@code variableName}.
194+
*
195+
* @param variableName the logical variable name.
196+
* @return the variable name used in the generated code.
197+
*/
198+
String localVariable(String variableName);
199+
200+
}
201+
202+
/**
203+
* Interface describing the origin of a constructor parameter. The parameter value can be obtained either from a
204+
* {@link #getReference() reference} variable, a {@link #getCodeBlock() code block} or a combination of both.
205+
*
206+
* @author Mark Paluch
207+
* @since 4.0
208+
*/
209+
interface ParameterOrigin {
210+
211+
/**
212+
* Construct a {@link ParameterOrigin} from the given {@link CodeBlock} and reference name.
213+
*
214+
* @param reference the reference name to obtain the parameter value from.
215+
* @param codeBlock the code block that is required to set up the parameter value.
216+
* @return a {@link ParameterOrigin} from the given {@link CodeBlock} and reference name.
217+
*/
218+
static ParameterOrigin of(String reference, CodeBlock codeBlock) {
219+
220+
Assert.hasText(reference, "Parameter reference must not be empty");
221+
222+
return new DefaultParameterOrigin(reference, codeBlock);
223+
}
224+
225+
/**
226+
* Construct a {@link ParameterOrigin} from the given {@link CodeBlock}.
227+
*
228+
* @param codeBlock the code block that produces the parameter value.
229+
* @return a {@link ParameterOrigin} from the given {@link CodeBlock}.
230+
*/
231+
static ParameterOrigin of(CodeBlock codeBlock) {
232+
233+
Assert.notNull(codeBlock, "CodeBlock reference must not be empty");
234+
235+
return new DefaultParameterOrigin(null, codeBlock);
236+
}
237+
238+
/**
239+
* Construct a {@link ParameterOrigin} from the given reference name.
240+
*
241+
* @param reference the reference name of the variable to obtain the parameter value from.
242+
* @return a {@link ParameterOrigin} from the given reference name.
243+
*/
244+
static ParameterOrigin ofReference(String reference) {
245+
246+
Assert.hasText(reference, "Parameter reference must not be empty");
247+
248+
return of(reference, CodeBlock.builder().build());
249+
}
250+
251+
/**
252+
* Obtain the reference name to obtain the parameter value from. Can be {@code null} if the parameter value is
253+
* solely obtained from the {@link #getCodeBlock() code block}.
254+
*
255+
* @return name of the reference or {@literal null} if absent.
256+
*/
257+
@Nullable
258+
String getReference();
259+
260+
/**
261+
* Obtain the code block to obtain the parameter value from. Never {@literal null}, can be empty.
262+
*/
263+
CodeBlock getCodeBlock();
264+
265+
}
92266
}

src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryCreator.java

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,10 @@ Map<String, ResolvableType> getAutowireFields() {
110110
return autowireFields;
111111
}
112112

113+
Map<String, ConstructorArgument> getConstructorArguments() {
114+
return generationMetadata.getConstructorArguments();
115+
}
116+
113117
RepositoryInformation getRepositoryInformation() {
114118
return repositoryInformation;
115119
}
@@ -304,6 +308,8 @@ private void contributeMethod(Method method, @Nullable MethodContributorFactory
304308
logger.trace("Skipping method [%s.%s] contribution, no MethodContributor available"
305309
.formatted(repositoryInformation.getRepositoryInterface().getName(), method.getName()));
306310
}
311+
312+
return;
307313
}
308314

309315
if (contributor.contributesMethodSpec() && !repositoryInformation.isReactiveRepository()) {
@@ -313,21 +319,6 @@ private void contributeMethod(Method method, @Nullable MethodContributorFactory
313319
}
314320
}
315321

316-
/**
317-
* Customizer interface to customize the AOT repository fragment constructor through
318-
* {@link AotRepositoryConstructorBuilder}.
319-
*/
320-
public interface ConstructorCustomizer {
321-
322-
/**
323-
* Apply customization ot the AOT repository fragment constructor.
324-
*
325-
* @param constructorBuilder the builder to be customized.
326-
*/
327-
void customize(AotRepositoryConstructorBuilder constructorBuilder);
328-
329-
}
330-
331322
/**
332323
* Factory interface to conditionally create {@link MethodContributor} instances. An implementation may decide whether
333324
* to return a {@link MethodContributor} or {@literal null}, if no method (code or metadata) should be contributed.

0 commit comments

Comments
 (0)