1
16 package org.springframework.data.spel;
17
18 import lombok.NonNull;
19 import lombok.RequiredArgsConstructor;
20
21 import java.util.Collection;
22 import java.util.Collections;
23 import java.util.HashMap;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.Optional;
27 import java.util.concurrent.ConcurrentHashMap;
28 import java.util.stream.Collectors;
29
30 import org.springframework.beans.factory.BeanFactory;
31 import org.springframework.beans.factory.ListableBeanFactory;
32 import org.springframework.context.expression.BeanFactoryResolver;
33 import org.springframework.core.annotation.AnnotationAwareOrderComparator;
34 import org.springframework.core.convert.TypeDescriptor;
35 import org.springframework.data.spel.EvaluationContextExtensionInformation.ExtensionTypeInformation;
36 import org.springframework.data.spel.EvaluationContextExtensionInformation.RootObjectInformation;
37 import org.springframework.data.spel.spi.EvaluationContextExtension;
38 import org.springframework.data.spel.spi.Function;
39 import org.springframework.data.util.Lazy;
40 import org.springframework.data.util.Optionals;
41 import org.springframework.expression.AccessException;
42 import org.springframework.expression.EvaluationContext;
43 import org.springframework.expression.MethodExecutor;
44 import org.springframework.expression.MethodResolver;
45 import org.springframework.expression.PropertyAccessor;
46 import org.springframework.expression.TypedValue;
47 import org.springframework.expression.spel.SpelEvaluationException;
48 import org.springframework.expression.spel.SpelMessage;
49 import org.springframework.expression.spel.support.ReflectivePropertyAccessor;
50 import org.springframework.expression.spel.support.StandardEvaluationContext;
51 import org.springframework.lang.Nullable;
52 import org.springframework.util.Assert;
53
54
64 @RequiredArgsConstructor
65 public class ExtensionAwareEvaluationContextProvider implements EvaluationContextProvider {
66
67 private final Map<Class<?>, EvaluationContextExtensionInformation> extensionInformationCache = new ConcurrentHashMap<>();
68
69 private final Lazy<? extends Collection<? extends EvaluationContextExtension>> extensions;
70 private ListableBeanFactory beanFactory;
71
72 ExtensionAwareEvaluationContextProvider() {
73 this(Collections.emptyList());
74 }
75
76
82 public ExtensionAwareEvaluationContextProvider(ListableBeanFactory beanFactory) {
83
84 this(Lazy.of(() -> getExtensionsFrom(beanFactory)));
85
86 this.beanFactory = beanFactory;
87 }
88
89
94 public ExtensionAwareEvaluationContextProvider(Collection<? extends EvaluationContextExtension> extensions) {
95 this(Lazy.of(extensions));
96 }
97
98
101 @Override
102 public StandardEvaluationContext getEvaluationContext(Object rootObject) {
103
104 StandardEvaluationContext context = new StandardEvaluationContext();
105
106 if (beanFactory != null) {
107 context.setBeanResolver(new BeanFactoryResolver(beanFactory));
108 }
109
110 ExtensionAwarePropertyAccessor accessor = new ExtensionAwarePropertyAccessor(extensions.get());
111
112 context.addPropertyAccessor(accessor);
113 context.addPropertyAccessor(new ReflectivePropertyAccessor());
114 context.addMethodResolver(accessor);
115
116 if (rootObject != null) {
117 context.setRootObject(rootObject);
118 }
119
120 return context;
121 }
122
123
129 private static Collection<? extends EvaluationContextExtension> getExtensionsFrom(ListableBeanFactory beanFactory) {
130 return beanFactory.getBeansOfType(EvaluationContextExtension.class, true, false).values();
131 }
132
133
140 private EvaluationContextExtensionInformation getOrCreateInformation(EvaluationContextExtension extension) {
141
142 Class<? extends EvaluationContextExtension> extensionType = extension.getClass();
143
144 return extensionInformationCache.computeIfAbsent(extensionType,
145 type -> new EvaluationContextExtensionInformation(extensionType));
146 }
147
148
154 private List<EvaluationContextExtensionAdapter> toAdapters(
155 Collection<? extends EvaluationContextExtension> extensions) {
156
157 return extensions.stream()
158 .sorted(AnnotationAwareOrderComparator.INSTANCE)
159 .map(it -> new EvaluationContextExtensionAdapter(it, getOrCreateInformation(it)))
160 .collect(Collectors.toList());
161 }
162
163
168 private class ExtensionAwarePropertyAccessor implements PropertyAccessor, MethodResolver {
169
170 private final List<EvaluationContextExtensionAdapter> adapters;
171 private final Map<String, EvaluationContextExtensionAdapter> adapterMap;
172
173
178 public ExtensionAwarePropertyAccessor(Collection<? extends EvaluationContextExtension> extensions) {
179
180 Assert.notNull(extensions, "Extensions must not be null!");
181
182 this.adapters = toAdapters(extensions);
183 this.adapterMap = adapters.stream()
184 .collect(Collectors.toMap(EvaluationContextExtensionAdapter::getExtensionId, it -> it));
185
186 Collections.reverse(this.adapters);
187 }
188
189
193 @Override
194 public boolean canRead(EvaluationContext context, @Nullable Object target, String name) {
195
196 if (target instanceof EvaluationContextExtension) {
197 return true;
198 }
199
200 if (adapterMap.containsKey(name)) {
201 return true;
202 }
203
204 return adapters.stream().anyMatch(it -> it.getProperties().containsKey(name));
205 }
206
207
211 @Override
212 public TypedValue read(EvaluationContext context, @Nullable Object target, String name) {
213
214 if (target instanceof EvaluationContextExtensionAdapter) {
215 return lookupPropertyFrom((EvaluationContextExtensionAdapter) target, name);
216 }
217
218 if (adapterMap.containsKey(name)) {
219 return new TypedValue(adapterMap.get(name));
220 }
221
222 return adapters.stream()
223 .filter(it -> it.getProperties().containsKey(name))
224 .map(it -> lookupPropertyFrom(it, name))
225 .findFirst().orElse(TypedValue.NULL);
226 }
227
228
232 @Nullable
233 @Override
234 public MethodExecutor resolve(EvaluationContext context, @Nullable Object target, final String name,
235 List<TypeDescriptor> argumentTypes) {
236
237 if (target instanceof EvaluationContextExtensionAdapter) {
238 return getMethodExecutor((EvaluationContextExtensionAdapter) target, name, argumentTypes).orElse(null);
239 }
240
241 return adapters.stream()
242 .flatMap(it -> Optionals.toStream(getMethodExecutor(it, name, argumentTypes)))
243 .findFirst().orElse(null);
244 }
245
246
250 @Override
251 public boolean canWrite(EvaluationContext context, @Nullable Object target, String name) {
252 return false;
253 }
254
255
259 @Override
260 public void write(EvaluationContext context, @Nullable Object target, String name, @Nullable Object newValue) {
261
262 }
263
264
268 @Nullable
269 @Override
270 public Class<?>[] getSpecificTargetClasses() {
271 return null;
272 }
273
274
282 private Optional<MethodExecutor> getMethodExecutor(EvaluationContextExtensionAdapter adapter, String name,
283 List<TypeDescriptor> argumentTypes) {
284 return adapter.getFunctions().get(name, argumentTypes).map(FunctionMethodExecutor::new);
285 }
286
287
295 private TypedValue lookupPropertyFrom(EvaluationContextExtensionAdapter extension, String name) {
296
297 Object value = extension.getProperties().get(name);
298
299 if (!(value instanceof Function)) {
300 return new TypedValue(value);
301 }
302
303 Function function = (Function) value;
304
305 try {
306 return new TypedValue(function.invoke(new Object[0]));
307 } catch (Exception e) {
308 throw new SpelEvaluationException(e, SpelMessage.FUNCTION_REFERENCE_CANNOT_BE_INVOKED, name,
309 function.getDeclaringClass());
310 }
311 }
312 }
313
314
320 @RequiredArgsConstructor
321 private static class FunctionMethodExecutor implements MethodExecutor {
322
323 private final @NonNull Function function;
324
325
329 @Override
330 public TypedValue execute(EvaluationContext context, Object target, Object... arguments) throws AccessException {
331
332 try {
333 return new TypedValue(function.invoke(arguments));
334 } catch (Exception e) {
335 throw new SpelEvaluationException(e, SpelMessage.FUNCTION_REFERENCE_CANNOT_BE_INVOKED, function.getName(),
336 function.getDeclaringClass());
337 }
338 }
339 }
340
341
349 private static class EvaluationContextExtensionAdapter {
350
351 private final EvaluationContextExtension extension;
352
353 private final Functions functions = new Functions();
354 private final Map<String, Object> properties;
355
356
363 public EvaluationContextExtensionAdapter(EvaluationContextExtension extension,
364 EvaluationContextExtensionInformation information) {
365
366 Assert.notNull(extension, "Extension must not be null!");
367 Assert.notNull(information, "Extension information must not be null!");
368
369 Optional<Object> target = Optional.ofNullable(extension.getRootObject());
370 ExtensionTypeInformation extensionTypeInformation = information.getExtensionTypeInformation();
371 RootObjectInformation rootObjectInformation = information.getRootObjectInformation(target);
372
373 functions.addAll(extension.getFunctions());
374 functions.addAll(rootObjectInformation.getFunctions(target));
375 functions.addAll(extensionTypeInformation.getFunctions());
376
377 this.properties = new HashMap<>();
378 this.properties.putAll(extensionTypeInformation.getProperties());
379 this.properties.putAll(rootObjectInformation.getProperties(target));
380 this.properties.putAll(extension.getProperties());
381
382 this.extension = extension;
383 }
384
385
390 String getExtensionId() {
391 return extension.getExtensionId();
392 }
393
394
399 Functions getFunctions() {
400 return this.functions;
401 }
402
403
408 public Map<String, Object> getProperties() {
409 return this.properties;
410 }
411 }
412 }
413