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}