001/*
002 * Copyright 2002-2018 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.expression.spel.support;
018
019import java.util.Arrays;
020import java.util.Collections;
021import java.util.HashMap;
022import java.util.List;
023import java.util.Map;
024
025import org.springframework.core.convert.ConversionService;
026import org.springframework.core.convert.TypeDescriptor;
027import org.springframework.expression.BeanResolver;
028import org.springframework.expression.ConstructorResolver;
029import org.springframework.expression.EvaluationContext;
030import org.springframework.expression.EvaluationException;
031import org.springframework.expression.MethodResolver;
032import org.springframework.expression.OperatorOverloader;
033import org.springframework.expression.PropertyAccessor;
034import org.springframework.expression.TypeComparator;
035import org.springframework.expression.TypeConverter;
036import org.springframework.expression.TypeLocator;
037import org.springframework.expression.TypedValue;
038import org.springframework.expression.spel.SpelEvaluationException;
039import org.springframework.expression.spel.SpelMessage;
040
041/**
042 * A basic implementation of {@link EvaluationContext} that focuses on a subset
043 * of essential SpEL features and customization options, targeting simple
044 * condition evaluation and in particular data binding scenarios.
045 *
046 * <p>In many cases, the full extent of the SpEL language is not required and
047 * should be meaningfully restricted. Examples include but are not limited to
048 * data binding expressions, property-based filters, and others. To that effect,
049 * {@code SimpleEvaluationContext} is tailored to support only a subset of the
050 * SpEL language syntax, e.g. excluding references to Java types, constructors,
051 * and bean references.
052 *
053 * <p>When creating a {@code SimpleEvaluationContext} you need to choose the
054 * level of support that you need for property access in SpEL expressions:
055 * <ul>
056 * <li>A custom {@code PropertyAccessor} (typically not reflection-based),
057 * potentially combined with a {@link DataBindingPropertyAccessor}</li>
058 * <li>Data binding properties for read-only access</li>
059 * <li>Data binding properties for read and write</li>
060 * </ul>
061 *
062 * <p>Conveniently, {@link SimpleEvaluationContext#forReadOnlyDataBinding()}
063 * enables read access to properties via {@link DataBindingPropertyAccessor};
064 * same for {@link SimpleEvaluationContext#forReadWriteDataBinding()} when
065 * write access is needed as well. Alternatively, configure custom accessors
066 * via {@link SimpleEvaluationContext#forPropertyAccessors}, and potentially
067 * activate method resolution and/or a type converter through the builder.
068 *
069 * <p>Note that {@code SimpleEvaluationContext} is typically not configured
070 * with a default root object. Instead it is meant to be created once and
071 * used repeatedly through {@code getValue} calls on a pre-compiled
072 * {@link org.springframework.expression.Expression} with both an
073 * {@code EvaluationContext} and a root object as arguments:
074 * {@link org.springframework.expression.Expression#getValue(EvaluationContext, Object)}.
075 *
076 * <p>For more power and flexibility, in particular for internal configuration
077 * scenarios, consider using {@link StandardEvaluationContext} instead.
078 *
079 * @author Rossen Stoyanchev
080 * @author Juergen Hoeller
081 * @since 4.3.15
082 * @see #forPropertyAccessors
083 * @see #forReadOnlyDataBinding()
084 * @see #forReadWriteDataBinding()
085 * @see StandardEvaluationContext
086 * @see StandardTypeConverter
087 * @see DataBindingPropertyAccessor
088 */
089public class SimpleEvaluationContext implements EvaluationContext {
090
091        private static final TypeLocator typeNotFoundTypeLocator = new TypeLocator() {
092                @Override
093                public Class<?> findType(String typeName) throws EvaluationException {
094                        throw new SpelEvaluationException(SpelMessage.TYPE_NOT_FOUND, typeName);
095                }
096        };
097
098
099        private final TypedValue rootObject;
100
101        private final List<PropertyAccessor> propertyAccessors;
102
103        private final List<MethodResolver> methodResolvers;
104
105        private final TypeConverter typeConverter;
106
107        private final TypeComparator typeComparator = new StandardTypeComparator();
108
109        private final OperatorOverloader operatorOverloader = new StandardOperatorOverloader();
110
111        private final Map<String, Object> variables = new HashMap<String, Object>();
112
113
114        private SimpleEvaluationContext(List<PropertyAccessor> accessors, List<MethodResolver> resolvers,
115                        TypeConverter converter, TypedValue rootObject) {
116
117                this.propertyAccessors = accessors;
118                this.methodResolvers = resolvers;
119                this.typeConverter = (converter != null ? converter : new StandardTypeConverter());
120                this.rootObject = (rootObject != null ? rootObject : TypedValue.NULL);
121        }
122
123
124        /**
125         * Return the specified root object, if any.
126         */
127        @Override
128        public TypedValue getRootObject() {
129                return this.rootObject;
130        }
131
132        /**
133         * Return the specified {@link PropertyAccessor} delegates, if any.
134         * @see #forPropertyAccessors
135         */
136        @Override
137        public List<PropertyAccessor> getPropertyAccessors() {
138                return this.propertyAccessors;
139        }
140
141        /**
142         * Return an empty list, always, since this context does not support the
143         * use of type references.
144         */
145        @Override
146        public List<ConstructorResolver> getConstructorResolvers() {
147                return Collections.emptyList();
148        }
149
150        /**
151         * Return the specified {@link MethodResolver} delegates, if any.
152         * @see Builder#withMethodResolvers
153         */
154        @Override
155        public List<MethodResolver> getMethodResolvers() {
156                return this.methodResolvers;
157        }
158
159        /**
160         * {@code SimpleEvaluationContext} does not support the use of bean references.
161         * @return always {@code null}
162         */
163        @Override
164        public BeanResolver getBeanResolver() {
165                return null;
166        }
167
168        /**
169         * {@code SimpleEvaluationContext} does not support use of type references.
170         * @return {@code TypeLocator} implementation that raises a
171         * {@link SpelEvaluationException} with {@link SpelMessage#TYPE_NOT_FOUND}.
172         */
173        @Override
174        public TypeLocator getTypeLocator() {
175                return typeNotFoundTypeLocator;
176        }
177
178        /**
179         * The configured {@link TypeConverter}.
180         * <p>By default this is {@link StandardTypeConverter}.
181         * @see Builder#withTypeConverter
182         * @see Builder#withConversionService
183         */
184        @Override
185        public TypeConverter getTypeConverter() {
186                return this.typeConverter;
187        }
188
189        /**
190         * Return an instance of {@link StandardTypeComparator}.
191         */
192        @Override
193        public TypeComparator getTypeComparator() {
194                return this.typeComparator;
195        }
196
197        /**
198         * Return an instance of {@link StandardOperatorOverloader}.
199         */
200        @Override
201        public OperatorOverloader getOperatorOverloader() {
202                return this.operatorOverloader;
203        }
204
205        @Override
206        public void setVariable(String name, Object value) {
207                this.variables.put(name, value);
208        }
209
210        @Override
211        public Object lookupVariable(String name) {
212                return this.variables.get(name);
213        }
214
215
216        /**
217         * Create a {@code SimpleEvaluationContext} for the specified {@link PropertyAccessor}
218         * delegates: typically a custom {@code PropertyAccessor} specific to a use case
219         * (e.g. attribute resolution in a custom data structure), potentially combined with
220         * a {@link DataBindingPropertyAccessor} if property dereferences are needed as well.
221         * @param accessors the accessor delegates to use
222         * @see DataBindingPropertyAccessor#forReadOnlyAccess()
223         * @see DataBindingPropertyAccessor#forReadWriteAccess()
224         */
225        public static Builder forPropertyAccessors(PropertyAccessor... accessors) {
226                for (PropertyAccessor accessor : accessors) {
227                        if (accessor.getClass() == ReflectivePropertyAccessor.class) {
228                                throw new IllegalArgumentException("SimpleEvaluationContext is not designed for use with a plain " +
229                                                "ReflectivePropertyAccessor. Consider using DataBindingPropertyAccessor or a custom subclass.");
230                        }
231                }
232                return new Builder(accessors);
233        }
234
235        /**
236         * Create a {@code SimpleEvaluationContext} for read-only access to
237         * public properties via {@link DataBindingPropertyAccessor}.
238         * @see DataBindingPropertyAccessor#forReadOnlyAccess()
239         * @see #forPropertyAccessors
240         */
241        public static Builder forReadOnlyDataBinding() {
242                return new Builder(DataBindingPropertyAccessor.forReadOnlyAccess());
243        }
244
245        /**
246         * Create a {@code SimpleEvaluationContext} for read-write access to
247         * public properties via {@link DataBindingPropertyAccessor}.
248         * @see DataBindingPropertyAccessor#forReadWriteAccess()
249         * @see #forPropertyAccessors
250         */
251        public static Builder forReadWriteDataBinding() {
252                return new Builder(DataBindingPropertyAccessor.forReadWriteAccess());
253        }
254
255
256        /**
257         * Builder for {@code SimpleEvaluationContext}.
258         */
259        public static class Builder {
260
261                private final List<PropertyAccessor> accessors;
262
263                private List<MethodResolver> resolvers = Collections.emptyList();
264
265                private TypeConverter typeConverter;
266
267                private TypedValue rootObject;
268
269                public Builder(PropertyAccessor... accessors) {
270                        this.accessors = Arrays.asList(accessors);
271                }
272
273                /**
274                 * Register the specified {@link MethodResolver} delegates for
275                 * a combination of property access and method resolution.
276                 * @param resolvers the resolver delegates to use
277                 * @see #withInstanceMethods()
278                 * @see SimpleEvaluationContext#forPropertyAccessors
279                 */
280                public Builder withMethodResolvers(MethodResolver... resolvers) {
281                        for (MethodResolver resolver : resolvers) {
282                                if (resolver.getClass() == ReflectiveMethodResolver.class) {
283                                        throw new IllegalArgumentException("SimpleEvaluationContext is not designed for use with a plain " +
284                                                        "ReflectiveMethodResolver. Consider using DataBindingMethodResolver or a custom subclass.");
285                                }
286                        }
287                        this.resolvers = Arrays.asList(resolvers);
288                        return this;
289                }
290
291                /**
292                 * Register a {@link DataBindingMethodResolver} for instance method invocation purposes
293                 * (i.e. not supporting static methods) in addition to the specified property accessors,
294                 * typically in combination with a {@link DataBindingPropertyAccessor}.
295                 * @see #withMethodResolvers
296                 * @see SimpleEvaluationContext#forReadOnlyDataBinding()
297                 * @see SimpleEvaluationContext#forReadWriteDataBinding()
298                 */
299                public Builder withInstanceMethods() {
300                        this.resolvers = Collections.singletonList(
301                                        (MethodResolver) DataBindingMethodResolver.forInstanceMethodInvocation());
302                        return this;
303                }
304
305
306                /**
307                 * Register a custom {@link ConversionService}.
308                 * <p>By default a {@link StandardTypeConverter} backed by a
309                 * {@link org.springframework.core.convert.support.DefaultConversionService} is used.
310                 * @see #withTypeConverter
311                 * @see StandardTypeConverter#StandardTypeConverter(ConversionService)
312                 */
313                public Builder withConversionService(ConversionService conversionService) {
314                        this.typeConverter = new StandardTypeConverter(conversionService);
315                        return this;
316                }
317                /**
318                 * Register a custom {@link TypeConverter}.
319                 * <p>By default a {@link StandardTypeConverter} backed by a
320                 * {@link org.springframework.core.convert.support.DefaultConversionService} is used.
321                 * @see #withConversionService
322                 * @see StandardTypeConverter#StandardTypeConverter()
323                 */
324                public Builder withTypeConverter(TypeConverter converter) {
325                        this.typeConverter = converter;
326                        return this;
327                }
328
329                /**
330                 * Specify a default root object to resolve against.
331                 * <p>Default is none, expecting an object argument at evaluation time.
332                 * @see org.springframework.expression.Expression#getValue(EvaluationContext)
333                 * @see org.springframework.expression.Expression#getValue(EvaluationContext, Object)
334                 */
335                public Builder withRootObject(Object rootObject) {
336                        this.rootObject = new TypedValue(rootObject);
337                        return this;
338                }
339
340                /**
341                 * Specify a typed root object to resolve against.
342                 * <p>Default is none, expecting an object argument at evaluation time.
343                 * @see org.springframework.expression.Expression#getValue(EvaluationContext)
344                 * @see org.springframework.expression.Expression#getValue(EvaluationContext, Object)
345                 */
346                public Builder withTypedRootObject(Object rootObject, TypeDescriptor typeDescriptor) {
347                        this.rootObject = new TypedValue(rootObject, typeDescriptor);
348                        return this;
349                }
350
351                public SimpleEvaluationContext build() {
352                        return new SimpleEvaluationContext(this.accessors, this.resolvers, this.typeConverter, this.rootObject);
353                }
354        }
355
356}