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.MethodResolver;
031import org.springframework.expression.OperatorOverloader;
032import org.springframework.expression.PropertyAccessor;
033import org.springframework.expression.TypeComparator;
034import org.springframework.expression.TypeConverter;
035import org.springframework.expression.TypeLocator;
036import org.springframework.expression.TypedValue;
037import org.springframework.expression.spel.SpelEvaluationException;
038import org.springframework.expression.spel.SpelMessage;
039import org.springframework.lang.Nullable;
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 final class SimpleEvaluationContext implements EvaluationContext {
090
091        private static final TypeLocator typeNotFoundTypeLocator = typeName -> {
092                throw new SpelEvaluationException(SpelMessage.TYPE_NOT_FOUND, typeName);
093        };
094
095
096        private final TypedValue rootObject;
097
098        private final List<PropertyAccessor> propertyAccessors;
099
100        private final List<MethodResolver> methodResolvers;
101
102        private final TypeConverter typeConverter;
103
104        private final TypeComparator typeComparator = new StandardTypeComparator();
105
106        private final OperatorOverloader operatorOverloader = new StandardOperatorOverloader();
107
108        private final Map<String, Object> variables = new HashMap<>();
109
110
111        private SimpleEvaluationContext(List<PropertyAccessor> accessors, List<MethodResolver> resolvers,
112                        @Nullable TypeConverter converter, @Nullable TypedValue rootObject) {
113
114                this.propertyAccessors = accessors;
115                this.methodResolvers = resolvers;
116                this.typeConverter = (converter != null ? converter : new StandardTypeConverter());
117                this.rootObject = (rootObject != null ? rootObject : TypedValue.NULL);
118        }
119
120
121        /**
122         * Return the specified root object, if any.
123         */
124        @Override
125        public TypedValue getRootObject() {
126                return this.rootObject;
127        }
128
129        /**
130         * Return the specified {@link PropertyAccessor} delegates, if any.
131         * @see #forPropertyAccessors
132         */
133        @Override
134        public List<PropertyAccessor> getPropertyAccessors() {
135                return this.propertyAccessors;
136        }
137
138        /**
139         * Return an empty list, always, since this context does not support the
140         * use of type references.
141         */
142        @Override
143        public List<ConstructorResolver> getConstructorResolvers() {
144                return Collections.emptyList();
145        }
146
147        /**
148         * Return the specified {@link MethodResolver} delegates, if any.
149         * @see Builder#withMethodResolvers
150         */
151        @Override
152        public List<MethodResolver> getMethodResolvers() {
153                return this.methodResolvers;
154        }
155
156        /**
157         * {@code SimpleEvaluationContext} does not support the use of bean references.
158         * @return always {@code null}
159         */
160        @Override
161        @Nullable
162        public BeanResolver getBeanResolver() {
163                return null;
164        }
165
166        /**
167         * {@code SimpleEvaluationContext} does not support use of type references.
168         * @return {@code TypeLocator} implementation that raises a
169         * {@link SpelEvaluationException} with {@link SpelMessage#TYPE_NOT_FOUND}.
170         */
171        @Override
172        public TypeLocator getTypeLocator() {
173                return typeNotFoundTypeLocator;
174        }
175
176        /**
177         * The configured {@link TypeConverter}.
178         * <p>By default this is {@link StandardTypeConverter}.
179         * @see Builder#withTypeConverter
180         * @see Builder#withConversionService
181         */
182        @Override
183        public TypeConverter getTypeConverter() {
184                return this.typeConverter;
185        }
186
187        /**
188         * Return an instance of {@link StandardTypeComparator}.
189         */
190        @Override
191        public TypeComparator getTypeComparator() {
192                return this.typeComparator;
193        }
194
195        /**
196         * Return an instance of {@link StandardOperatorOverloader}.
197         */
198        @Override
199        public OperatorOverloader getOperatorOverloader() {
200                return this.operatorOverloader;
201        }
202
203        @Override
204        public void setVariable(String name, @Nullable Object value) {
205                this.variables.put(name, value);
206        }
207
208        @Override
209        @Nullable
210        public Object lookupVariable(String name) {
211                return this.variables.get(name);
212        }
213
214
215        /**
216         * Create a {@code SimpleEvaluationContext} for the specified {@link PropertyAccessor}
217         * delegates: typically a custom {@code PropertyAccessor} specific to a use case
218         * (e.g. attribute resolution in a custom data structure), potentially combined with
219         * a {@link DataBindingPropertyAccessor} if property dereferences are needed as well.
220         * @param accessors the accessor delegates to use
221         * @see DataBindingPropertyAccessor#forReadOnlyAccess()
222         * @see DataBindingPropertyAccessor#forReadWriteAccess()
223         */
224        public static Builder forPropertyAccessors(PropertyAccessor... accessors) {
225                for (PropertyAccessor accessor : accessors) {
226                        if (accessor.getClass() == ReflectivePropertyAccessor.class) {
227                                throw new IllegalArgumentException("SimpleEvaluationContext is not designed for use with a plain " +
228                                                "ReflectivePropertyAccessor. Consider using DataBindingPropertyAccessor or a custom subclass.");
229                        }
230                }
231                return new Builder(accessors);
232        }
233
234        /**
235         * Create a {@code SimpleEvaluationContext} for read-only access to
236         * public properties via {@link DataBindingPropertyAccessor}.
237         * @see DataBindingPropertyAccessor#forReadOnlyAccess()
238         * @see #forPropertyAccessors
239         */
240        public static Builder forReadOnlyDataBinding() {
241                return new Builder(DataBindingPropertyAccessor.forReadOnlyAccess());
242        }
243
244        /**
245         * Create a {@code SimpleEvaluationContext} for read-write access to
246         * public properties via {@link DataBindingPropertyAccessor}.
247         * @see DataBindingPropertyAccessor#forReadWriteAccess()
248         * @see #forPropertyAccessors
249         */
250        public static Builder forReadWriteDataBinding() {
251                return new Builder(DataBindingPropertyAccessor.forReadWriteAccess());
252        }
253
254
255        /**
256         * Builder for {@code SimpleEvaluationContext}.
257         */
258        public static class Builder {
259
260                private final List<PropertyAccessor> accessors;
261
262                private List<MethodResolver> resolvers = Collections.emptyList();
263
264                @Nullable
265                private TypeConverter typeConverter;
266
267                @Nullable
268                private TypedValue rootObject;
269
270                public Builder(PropertyAccessor... accessors) {
271                        this.accessors = Arrays.asList(accessors);
272                }
273
274                /**
275                 * Register the specified {@link MethodResolver} delegates for
276                 * a combination of property access and method resolution.
277                 * @param resolvers the resolver delegates to use
278                 * @see #withInstanceMethods()
279                 * @see SimpleEvaluationContext#forPropertyAccessors
280                 */
281                public Builder withMethodResolvers(MethodResolver... resolvers) {
282                        for (MethodResolver resolver : resolvers) {
283                                if (resolver.getClass() == ReflectiveMethodResolver.class) {
284                                        throw new IllegalArgumentException("SimpleEvaluationContext is not designed for use with a plain " +
285                                                        "ReflectiveMethodResolver. Consider using DataBindingMethodResolver or a custom subclass.");
286                                }
287                        }
288                        this.resolvers = Arrays.asList(resolvers);
289                        return this;
290                }
291
292                /**
293                 * Register a {@link DataBindingMethodResolver} for instance method invocation purposes
294                 * (i.e. not supporting static methods) in addition to the specified property accessors,
295                 * typically in combination with a {@link DataBindingPropertyAccessor}.
296                 * @see #withMethodResolvers
297                 * @see SimpleEvaluationContext#forReadOnlyDataBinding()
298                 * @see SimpleEvaluationContext#forReadWriteDataBinding()
299                 */
300                public Builder withInstanceMethods() {
301                        this.resolvers = Collections.singletonList(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}