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}