1 /*
2  * Copyright 2014-2020 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.spel;
17
18 import static org.springframework.data.util.StreamUtils.*;
19
20 import lombok.Getter;
21 import lombok.RequiredArgsConstructor;
22
23 import java.beans.PropertyDescriptor;
24 import java.lang.reflect.Field;
25 import java.lang.reflect.Method;
26 import java.lang.reflect.Modifier;
27 import java.util.ArrayList;
28 import java.util.Collection;
29 import java.util.Collections;
30 import java.util.HashMap;
31 import java.util.HashSet;
32 import java.util.Map;
33 import java.util.Optional;
34
35 import org.springframework.beans.BeanUtils;
36 import org.springframework.data.spel.EvaluationContextExtensionInformation.ExtensionTypeInformation.PublicMethodAndFieldFilter;
37 import org.springframework.data.spel.spi.EvaluationContextExtension;
38 import org.springframework.data.spel.spi.Function;
39 import org.springframework.data.util.Streamable;
40 import org.springframework.util.Assert;
41 import org.springframework.util.CollectionUtils;
42 import org.springframework.util.LinkedMultiValueMap;
43 import org.springframework.util.MultiValueMap;
44 import org.springframework.util.ReflectionUtils;
45 import org.springframework.util.ReflectionUtils.FieldFilter;
46 import org.springframework.util.ReflectionUtils.MethodFilter;
47
48 /**
49  * Inspects the configured {@link EvaluationContextExtension} type for static methods and fields to avoid repeated
50  * reflection lookups. Also inspects the return type of the {@link EvaluationContextExtension#getRootObject()} method
51  * and captures the methods declared on it as {@link Function}s.
52  * <p>
53  * The type basically allows us to cache the type based information within
54  * {@link ExtensionAwareEvaluationContextProvider} to avoid repeated reflection lookups for every creation of an
55  * {@link org.springframework.expression.EvaluationContext}.
56  *
57  * @author Oliver Gierke
58  * @author Christoph Strobl
59  * @author Jens Schauder
60  * @since 2.1
61  */

62 class EvaluationContextExtensionInformation {
63
64     private final ExtensionTypeInformation extensionTypeInformation;
65     private final Optional<RootObjectInformation> rootObjectInformation;
66
67     /**
68      * Creates a new {@link EvaluationContextExtension} for the given extension type.
69      *
70      * @param type must not be {@literal null}.
71      */

72     public EvaluationContextExtensionInformation(Class<? extends EvaluationContextExtension> type) {
73
74         Assert.notNull(type, "Extension type must not be null!");
75
76         Class<?> rootObjectType = org.springframework.data.util.ReflectionUtils.findRequiredMethod(type, "getRootObject")
77                 .getReturnType();
78
79         this.rootObjectInformation = Optional
80                 .ofNullable(Object.class.equals(rootObjectType) ? null : new RootObjectInformation(rootObjectType));
81         this.extensionTypeInformation = new ExtensionTypeInformation(type);
82     }
83
84     /**
85      * Returns the {@link ExtensionTypeInformation} for the extension.
86      *
87      * @return
88      */

89     public ExtensionTypeInformation getExtensionTypeInformation() {
90         return this.extensionTypeInformation;
91     }
92
93     /**
94      * Returns the {@link RootObjectInformation} for the given target object. If the information has been pre-computed
95      * earlier, the existing one will be used.
96      *
97      * @param target
98      * @return
99      */

100     public RootObjectInformation getRootObjectInformation(Optional<Object> target) {
101
102         return target.map(it -> rootObjectInformation.orElseGet(() -> new RootObjectInformation(it.getClass())))
103                 .orElse(RootObjectInformation.NONE);
104     }
105
106     /**
107      * Static information about the given {@link EvaluationContextExtension} type. Discovers public static methods and
108      * fields. The fields' values are obtained directly, the methods are exposed {@link Function} invocations.
109      *
110      * @author Oliver Gierke
111      */

112     @Getter
113     public static class ExtensionTypeInformation {
114
115         /**
116          * The statically defined properties of the extension type.
117          *
118          * @return the properties will never be {@literal null}.
119          */

120         private final Map<String, Object> properties;
121
122         /**
123          * The statically exposed functions of the extension type.
124          *
125          * @return the functions will never be {@literal null}.
126          */

127         private final MultiValueMap<String, Function> functions;
128
129         /**
130          * Creates a new {@link ExtensionTypeInformation} fir the given type.
131          *
132          * @param type must not be {@literal null}.
133          */

134         public ExtensionTypeInformation(Class<? extends EvaluationContextExtension> type) {
135
136             Assert.notNull(type, "Extension type must not be null!");
137
138             this.functions = discoverDeclaredFunctions(type);
139             this.properties = discoverDeclaredProperties(type);
140         }
141
142         private static MultiValueMap<String, Function> discoverDeclaredFunctions(Class<?> type) {
143
144             MultiValueMap<String, Function> map = CollectionUtils.toMultiValueMap(new HashMap<>());
145
146             ReflectionUtils.doWithMethods(type, //
147                     method -> map.add(method.getName(), new Function(method, null)), //
148                     PublicMethodAndFieldFilter.STATIC);
149
150             return CollectionUtils.unmodifiableMultiValueMap(map);
151         }
152
153         @RequiredArgsConstructor
154         static class PublicMethodAndFieldFilter implements MethodFilter, FieldFilter {
155
156             public static final PublicMethodAndFieldFilter STATIC = new PublicMethodAndFieldFilter(true);
157             public static final PublicMethodAndFieldFilter NON_STATIC = new PublicMethodAndFieldFilter(false);
158
159             private final boolean staticOnly;
160
161             /*
162              * (non-Javadoc)
163              * @see org.springframework.util.ReflectionUtils.MethodFilter#matches(java.lang.reflect.Method)
164              */

165             @Override
166             public boolean matches(Method method) {
167
168                 if (ReflectionUtils.isObjectMethod(method)) {
169                     return false;
170                 }
171
172                 boolean methodStatic = Modifier.isStatic(method.getModifiers());
173                 boolean staticMatch = staticOnly ? methodStatic : !methodStatic;
174
175                 return Modifier.isPublic(method.getModifiers()) && staticMatch;
176             }
177
178             /*
179              * (non-Javadoc)
180              * @see org.springframework.util.ReflectionUtils.FieldFilter#matches(java.lang.reflect.Field)
181              */

182             @Override
183             public boolean matches(Field field) {
184
185                 boolean fieldStatic = Modifier.isStatic(field.getModifiers());
186                 boolean staticMatch = staticOnly ? fieldStatic : !fieldStatic;
187
188                 return Modifier.isPublic(field.getModifiers()) && staticMatch;
189             }
190         }
191     }
192
193     /**
194      * Information about the root object of an extension.
195      *
196      * @author Oliver Gierke
197      */

198     static class RootObjectInformation {
199
200         private static final RootObjectInformation NONE = new RootObjectInformation(Object.class);
201
202         private final Map<String, Method> accessors;
203         private final Collection<Method> methods;
204         private final Collection<Field> fields;
205
206         /**
207          * Creates a new {@link RootObjectInformation} for the given type. Inspects public methods and fields to register
208          * them as {@link Function}s and properties.
209          *
210          * @param type must not be {@literal null}.
211          */

212         public RootObjectInformation(Class<?> type) {
213
214             Assert.notNull(type, "Type must not be null!");
215
216             this.accessors = new HashMap<>();
217             this.methods = new HashSet<>();
218             this.fields = new ArrayList<>();
219
220             if (Object.class.equals(type)) {
221                 return;
222             }
223
224             Streamable<PropertyDescriptor> descriptors = Streamable.of(BeanUtils.getPropertyDescriptors(type));
225
226             ReflectionUtils.doWithMethods(type, method -> {
227
228                 RootObjectInformation.this.methods.add(method);
229
230                 descriptors.stream()//
231                         .filter(it -> method.equals(it.getReadMethod()))//
232                         .forEach(it -> RootObjectInformation.this.accessors.put(it.getName(), method));
233
234             }, PublicMethodAndFieldFilter.NON_STATIC);
235
236             ReflectionUtils.doWithFields(type, RootObjectInformation.this.fields::add, PublicMethodAndFieldFilter.NON_STATIC);
237         }
238
239         /**
240          * Returns {@link Function} instances that wrap method invocations on the given target object.
241          *
242          * @param target can be {@literal null}.
243          * @return the methods
244          */

245         public MultiValueMap<String, Function> getFunctions(Optional<Object> target) {
246             return target.map(this::getFunctions).orElseGet(() -> new LinkedMultiValueMap<>());
247         }
248
249         private MultiValueMap<String, Function> getFunctions(Object target) {
250             return methods.stream().collect(toMultiMap(Method::getName, m -> new Function(m, target)));
251         }
252
253         /**
254          * Returns the properties of the target object. This will also include {@link Function} instances for all properties
255          * with accessor methods that need to be resolved downstream.
256          *
257          * @return the properties
258          */

259         public Map<String, Object> getProperties(Optional<Object> target) {
260
261             return target.map(it -> {
262
263                 Map<String, Object> properties = new HashMap<>();
264
265                 accessors.entrySet().stream()
266                         .forEach(method -> properties.put(method.getKey(), new Function(method.getValue(), it)));
267                 fields.stream().forEach(field -> properties.put(field.getName(), ReflectionUtils.getField(field, it)));
268
269                 return Collections.unmodifiableMap(properties);
270
271             }).orElseGet(Collections::emptyMap);
272         }
273     }
274
275     private static Map<String, Object> discoverDeclaredProperties(Class<?> type) {
276
277         Map<String, Object> map = new HashMap<>();
278
279         ReflectionUtils.doWithFields(type, field -> map.put(field.getName(), field.get(null)),
280                 PublicMethodAndFieldFilter.STATIC);
281
282         return map.isEmpty() ? Collections.emptyMap() : Collections.unmodifiableMap(map);
283     }
284 }
285