001/* 002 * Copyright 2002-2020 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.standard; 018 019import java.util.concurrent.atomic.AtomicInteger; 020 021import org.springframework.core.convert.TypeDescriptor; 022import org.springframework.expression.EvaluationContext; 023import org.springframework.expression.EvaluationException; 024import org.springframework.expression.Expression; 025import org.springframework.expression.TypedValue; 026import org.springframework.expression.common.ExpressionUtils; 027import org.springframework.expression.spel.CompiledExpression; 028import org.springframework.expression.spel.ExpressionState; 029import org.springframework.expression.spel.SpelCompilerMode; 030import org.springframework.expression.spel.SpelEvaluationException; 031import org.springframework.expression.spel.SpelMessage; 032import org.springframework.expression.spel.SpelNode; 033import org.springframework.expression.spel.SpelParserConfiguration; 034import org.springframework.expression.spel.ast.SpelNodeImpl; 035import org.springframework.expression.spel.support.StandardEvaluationContext; 036import org.springframework.util.Assert; 037 038/** 039 * A {@code SpelExpression} represents a parsed (valid) expression that is ready to be 040 * evaluated in a specified context. An expression can be evaluated standalone or in a 041 * specified context. During expression evaluation the context may be asked to resolve 042 * references to types, beans, properties, and methods. 043 * 044 * @author Andy Clement 045 * @author Juergen Hoeller 046 * @since 3.0 047 */ 048public class SpelExpression implements Expression { 049 050 // Number of times to interpret an expression before compiling it 051 private static final int INTERPRETED_COUNT_THRESHOLD = 100; 052 053 // Number of times to try compiling an expression before giving up 054 private static final int FAILED_ATTEMPTS_THRESHOLD = 100; 055 056 057 private final String expression; 058 059 private final SpelNodeImpl ast; 060 061 private final SpelParserConfiguration configuration; 062 063 // The default context is used if no override is supplied by the user 064 private EvaluationContext evaluationContext; 065 066 // Holds the compiled form of the expression (if it has been compiled) 067 private volatile CompiledExpression compiledAst; 068 069 // Count of many times as the expression been interpreted - can trigger compilation 070 // when certain limit reached 071 private final AtomicInteger interpretedCount = new AtomicInteger(0); 072 073 // The number of times compilation was attempted and failed - enables us to eventually 074 // give up trying to compile it when it just doesn't seem to be possible. 075 private final AtomicInteger failedAttempts = new AtomicInteger(0); 076 077 078 /** 079 * Construct an expression, only used by the parser. 080 */ 081 public SpelExpression(String expression, SpelNodeImpl ast, SpelParserConfiguration configuration) { 082 this.expression = expression; 083 this.ast = ast; 084 this.configuration = configuration; 085 } 086 087 088 /** 089 * Set the evaluation context that will be used if none is specified on an evaluation call. 090 * @param evaluationContext the evaluation context to use 091 */ 092 public void setEvaluationContext(EvaluationContext evaluationContext) { 093 this.evaluationContext = evaluationContext; 094 } 095 096 /** 097 * Return the default evaluation context that will be used if none is supplied on an evaluation call. 098 * @return the default evaluation context 099 */ 100 public EvaluationContext getEvaluationContext() { 101 if (this.evaluationContext == null) { 102 this.evaluationContext = new StandardEvaluationContext(); 103 } 104 return this.evaluationContext; 105 } 106 107 108 // implementing Expression 109 110 @Override 111 public String getExpressionString() { 112 return this.expression; 113 } 114 115 @Override 116 public Object getValue() throws EvaluationException { 117 CompiledExpression compiledAst = this.compiledAst; 118 if (compiledAst != null) { 119 try { 120 EvaluationContext context = getEvaluationContext(); 121 return compiledAst.getValue(context.getRootObject().getValue(), context); 122 } 123 catch (Throwable ex) { 124 // If running in mixed mode, revert to interpreted 125 if (this.configuration.getCompilerMode() == SpelCompilerMode.MIXED) { 126 this.compiledAst = null; 127 this.interpretedCount.set(0); 128 } 129 else { 130 // Running in SpelCompilerMode.immediate mode - propagate exception to caller 131 throw new SpelEvaluationException(ex, SpelMessage.EXCEPTION_RUNNING_COMPILED_EXPRESSION); 132 } 133 } 134 } 135 136 ExpressionState expressionState = new ExpressionState(getEvaluationContext(), this.configuration); 137 Object result = this.ast.getValue(expressionState); 138 checkCompile(expressionState); 139 return result; 140 } 141 142 @SuppressWarnings("unchecked") 143 @Override 144 public <T> T getValue(Class<T> expectedResultType) throws EvaluationException { 145 CompiledExpression compiledAst = this.compiledAst; 146 if (compiledAst != null) { 147 try { 148 EvaluationContext context = getEvaluationContext(); 149 Object result = compiledAst.getValue(context.getRootObject().getValue(), context); 150 if (expectedResultType == null) { 151 return (T) result; 152 } 153 else { 154 return ExpressionUtils.convertTypedValue( 155 getEvaluationContext(), new TypedValue(result), expectedResultType); 156 } 157 } 158 catch (Throwable ex) { 159 // If running in mixed mode, revert to interpreted 160 if (this.configuration.getCompilerMode() == SpelCompilerMode.MIXED) { 161 this.compiledAst = null; 162 this.interpretedCount.set(0); 163 } 164 else { 165 // Running in SpelCompilerMode.immediate mode - propagate exception to caller 166 throw new SpelEvaluationException(ex, SpelMessage.EXCEPTION_RUNNING_COMPILED_EXPRESSION); 167 } 168 } 169 } 170 171 ExpressionState expressionState = new ExpressionState(getEvaluationContext(), this.configuration); 172 TypedValue typedResultValue = this.ast.getTypedValue(expressionState); 173 checkCompile(expressionState); 174 return ExpressionUtils.convertTypedValue( 175 expressionState.getEvaluationContext(), typedResultValue, expectedResultType); 176 } 177 178 @Override 179 public Object getValue(Object rootObject) throws EvaluationException { 180 CompiledExpression compiledAst = this.compiledAst; 181 if (compiledAst != null) { 182 try { 183 return compiledAst.getValue(rootObject, getEvaluationContext()); 184 } 185 catch (Throwable ex) { 186 // If running in mixed mode, revert to interpreted 187 if (this.configuration.getCompilerMode() == SpelCompilerMode.MIXED) { 188 this.compiledAst = null; 189 this.interpretedCount.set(0); 190 } 191 else { 192 // Running in SpelCompilerMode.immediate mode - propagate exception to caller 193 throw new SpelEvaluationException(ex, SpelMessage.EXCEPTION_RUNNING_COMPILED_EXPRESSION); 194 } 195 } 196 } 197 198 ExpressionState expressionState = 199 new ExpressionState(getEvaluationContext(), toTypedValue(rootObject), this.configuration); 200 Object result = this.ast.getValue(expressionState); 201 checkCompile(expressionState); 202 return result; 203 } 204 205 @SuppressWarnings("unchecked") 206 @Override 207 public <T> T getValue(Object rootObject, Class<T> expectedResultType) throws EvaluationException { 208 CompiledExpression compiledAst = this.compiledAst; 209 if (compiledAst != null) { 210 try { 211 Object result = compiledAst.getValue(rootObject, getEvaluationContext()); 212 if (expectedResultType == null) { 213 return (T)result; 214 } 215 else { 216 return ExpressionUtils.convertTypedValue( 217 getEvaluationContext(), new TypedValue(result), expectedResultType); 218 } 219 } 220 catch (Throwable ex) { 221 // If running in mixed mode, revert to interpreted 222 if (this.configuration.getCompilerMode() == SpelCompilerMode.MIXED) { 223 this.compiledAst = null; 224 this.interpretedCount.set(0); 225 } 226 else { 227 // Running in SpelCompilerMode.immediate mode - propagate exception to caller 228 throw new SpelEvaluationException(ex, SpelMessage.EXCEPTION_RUNNING_COMPILED_EXPRESSION); 229 } 230 } 231 } 232 233 ExpressionState expressionState = 234 new ExpressionState(getEvaluationContext(), toTypedValue(rootObject), this.configuration); 235 TypedValue typedResultValue = this.ast.getTypedValue(expressionState); 236 checkCompile(expressionState); 237 return ExpressionUtils.convertTypedValue( 238 expressionState.getEvaluationContext(), typedResultValue, expectedResultType); 239 } 240 241 @Override 242 public Object getValue(EvaluationContext context) throws EvaluationException { 243 Assert.notNull(context, "EvaluationContext is required"); 244 245 CompiledExpression compiledAst = this.compiledAst; 246 if (compiledAst != null) { 247 try { 248 return compiledAst.getValue(context.getRootObject().getValue(), context); 249 } 250 catch (Throwable ex) { 251 // If running in mixed mode, revert to interpreted 252 if (this.configuration.getCompilerMode() == SpelCompilerMode.MIXED) { 253 this.compiledAst = null; 254 this.interpretedCount.set(0); 255 } 256 else { 257 // Running in SpelCompilerMode.immediate mode - propagate exception to caller 258 throw new SpelEvaluationException(ex, SpelMessage.EXCEPTION_RUNNING_COMPILED_EXPRESSION); 259 } 260 } 261 } 262 263 ExpressionState expressionState = new ExpressionState(context, this.configuration); 264 Object result = this.ast.getValue(expressionState); 265 checkCompile(expressionState); 266 return result; 267 } 268 269 @SuppressWarnings("unchecked") 270 @Override 271 public <T> T getValue(EvaluationContext context, Class<T> expectedResultType) throws EvaluationException { 272 Assert.notNull(context, "EvaluationContext is required"); 273 274 CompiledExpression compiledAst = this.compiledAst; 275 if (compiledAst != null) { 276 try { 277 Object result = compiledAst.getValue(context.getRootObject().getValue(), context); 278 if (expectedResultType != null) { 279 return ExpressionUtils.convertTypedValue(context, new TypedValue(result), expectedResultType); 280 } 281 else { 282 return (T) result; 283 } 284 } 285 catch (Throwable ex) { 286 // If running in mixed mode, revert to interpreted 287 if (this.configuration.getCompilerMode() == SpelCompilerMode.MIXED) { 288 this.compiledAst = null; 289 this.interpretedCount.set(0); 290 } 291 else { 292 // Running in SpelCompilerMode.immediate mode - propagate exception to caller 293 throw new SpelEvaluationException(ex, SpelMessage.EXCEPTION_RUNNING_COMPILED_EXPRESSION); 294 } 295 } 296 } 297 298 ExpressionState expressionState = new ExpressionState(context, this.configuration); 299 TypedValue typedResultValue = this.ast.getTypedValue(expressionState); 300 checkCompile(expressionState); 301 return ExpressionUtils.convertTypedValue(context, typedResultValue, expectedResultType); 302 } 303 304 @Override 305 public Object getValue(EvaluationContext context, Object rootObject) throws EvaluationException { 306 Assert.notNull(context, "EvaluationContext is required"); 307 308 CompiledExpression compiledAst = this.compiledAst; 309 if (compiledAst != null) { 310 try { 311 return compiledAst.getValue(rootObject, context); 312 } 313 catch (Throwable ex) { 314 // If running in mixed mode, revert to interpreted 315 if (this.configuration.getCompilerMode() == SpelCompilerMode.MIXED) { 316 this.compiledAst = null; 317 this.interpretedCount.set(0); 318 } 319 else { 320 // Running in SpelCompilerMode.immediate mode - propagate exception to caller 321 throw new SpelEvaluationException(ex, SpelMessage.EXCEPTION_RUNNING_COMPILED_EXPRESSION); 322 } 323 } 324 } 325 326 ExpressionState expressionState = new ExpressionState(context, toTypedValue(rootObject), this.configuration); 327 Object result = this.ast.getValue(expressionState); 328 checkCompile(expressionState); 329 return result; 330 } 331 332 @SuppressWarnings("unchecked") 333 @Override 334 public <T> T getValue(EvaluationContext context, Object rootObject, Class<T> expectedResultType) 335 throws EvaluationException { 336 337 Assert.notNull(context, "EvaluationContext is required"); 338 339 CompiledExpression compiledAst = this.compiledAst; 340 if (compiledAst != null) { 341 try { 342 Object result = compiledAst.getValue(rootObject, context); 343 if (expectedResultType != null) { 344 return ExpressionUtils.convertTypedValue(context, new TypedValue(result), expectedResultType); 345 } 346 else { 347 return (T) result; 348 } 349 } 350 catch (Throwable ex) { 351 // If running in mixed mode, revert to interpreted 352 if (this.configuration.getCompilerMode() == SpelCompilerMode.MIXED) { 353 this.compiledAst = null; 354 this.interpretedCount.set(0); 355 } 356 else { 357 // Running in SpelCompilerMode.immediate mode - propagate exception to caller 358 throw new SpelEvaluationException(ex, SpelMessage.EXCEPTION_RUNNING_COMPILED_EXPRESSION); 359 } 360 } 361 } 362 363 ExpressionState expressionState = new ExpressionState(context, toTypedValue(rootObject), this.configuration); 364 TypedValue typedResultValue = this.ast.getTypedValue(expressionState); 365 checkCompile(expressionState); 366 return ExpressionUtils.convertTypedValue(context, typedResultValue, expectedResultType); 367 } 368 369 @Override 370 public Class<?> getValueType() throws EvaluationException { 371 return getValueType(getEvaluationContext()); 372 } 373 374 @Override 375 public Class<?> getValueType(Object rootObject) throws EvaluationException { 376 return getValueType(getEvaluationContext(), rootObject); 377 } 378 379 @Override 380 public Class<?> getValueType(EvaluationContext context) throws EvaluationException { 381 Assert.notNull(context, "EvaluationContext is required"); 382 ExpressionState expressionState = new ExpressionState(context, this.configuration); 383 TypeDescriptor typeDescriptor = this.ast.getValueInternal(expressionState).getTypeDescriptor(); 384 return (typeDescriptor != null ? typeDescriptor.getType() : null); 385 } 386 387 @Override 388 public Class<?> getValueType(EvaluationContext context, Object rootObject) throws EvaluationException { 389 ExpressionState expressionState = new ExpressionState(context, toTypedValue(rootObject), this.configuration); 390 TypeDescriptor typeDescriptor = this.ast.getValueInternal(expressionState).getTypeDescriptor(); 391 return (typeDescriptor != null ? typeDescriptor.getType() : null); 392 } 393 394 @Override 395 public TypeDescriptor getValueTypeDescriptor() throws EvaluationException { 396 return getValueTypeDescriptor(getEvaluationContext()); 397 } 398 399 @Override 400 public TypeDescriptor getValueTypeDescriptor(Object rootObject) throws EvaluationException { 401 ExpressionState expressionState = 402 new ExpressionState(getEvaluationContext(), toTypedValue(rootObject), this.configuration); 403 return this.ast.getValueInternal(expressionState).getTypeDescriptor(); 404 } 405 406 @Override 407 public TypeDescriptor getValueTypeDescriptor(EvaluationContext context) throws EvaluationException { 408 Assert.notNull(context, "EvaluationContext is required"); 409 ExpressionState expressionState = new ExpressionState(context, this.configuration); 410 return this.ast.getValueInternal(expressionState).getTypeDescriptor(); 411 } 412 413 @Override 414 public TypeDescriptor getValueTypeDescriptor(EvaluationContext context, Object rootObject) 415 throws EvaluationException { 416 417 Assert.notNull(context, "EvaluationContext is required"); 418 ExpressionState expressionState = new ExpressionState(context, toTypedValue(rootObject), this.configuration); 419 return this.ast.getValueInternal(expressionState).getTypeDescriptor(); 420 } 421 422 @Override 423 public boolean isWritable(Object rootObject) throws EvaluationException { 424 return this.ast.isWritable( 425 new ExpressionState(getEvaluationContext(), toTypedValue(rootObject), this.configuration)); 426 } 427 428 @Override 429 public boolean isWritable(EvaluationContext context) throws EvaluationException { 430 Assert.notNull(context, "EvaluationContext is required"); 431 return this.ast.isWritable(new ExpressionState(context, this.configuration)); 432 } 433 434 @Override 435 public boolean isWritable(EvaluationContext context, Object rootObject) throws EvaluationException { 436 Assert.notNull(context, "EvaluationContext is required"); 437 return this.ast.isWritable(new ExpressionState(context, toTypedValue(rootObject), this.configuration)); 438 } 439 440 @Override 441 public void setValue(Object rootObject, Object value) throws EvaluationException { 442 this.ast.setValue( 443 new ExpressionState(getEvaluationContext(), toTypedValue(rootObject), this.configuration), value); 444 } 445 446 @Override 447 public void setValue(EvaluationContext context, Object value) throws EvaluationException { 448 Assert.notNull(context, "EvaluationContext is required"); 449 this.ast.setValue(new ExpressionState(context, this.configuration), value); 450 } 451 452 @Override 453 public void setValue(EvaluationContext context, Object rootObject, Object value) 454 throws EvaluationException { 455 456 Assert.notNull(context, "EvaluationContext is required"); 457 this.ast.setValue(new ExpressionState(context, toTypedValue(rootObject), this.configuration), value); 458 } 459 460 461 /** 462 * Compile the expression if it has been evaluated more than the threshold number 463 * of times to trigger compilation. 464 * @param expressionState the expression state used to determine compilation mode 465 */ 466 private void checkCompile(ExpressionState expressionState) { 467 this.interpretedCount.incrementAndGet(); 468 SpelCompilerMode compilerMode = expressionState.getConfiguration().getCompilerMode(); 469 if (compilerMode != SpelCompilerMode.OFF) { 470 if (compilerMode == SpelCompilerMode.IMMEDIATE) { 471 if (this.interpretedCount.get() > 1) { 472 compileExpression(); 473 } 474 } 475 else { 476 // compilerMode = SpelCompilerMode.MIXED 477 if (this.interpretedCount.get() > INTERPRETED_COUNT_THRESHOLD) { 478 compileExpression(); 479 } 480 } 481 } 482 } 483 484 /** 485 * Perform expression compilation. This will only succeed once exit descriptors for 486 * all nodes have been determined. If the compilation fails and has failed more than 487 * 100 times the expression is no longer considered suitable for compilation. 488 * @return whether this expression has been successfully compiled 489 */ 490 public boolean compileExpression() { 491 CompiledExpression compiledAst = this.compiledAst; 492 if (compiledAst != null) { 493 // Previously compiled 494 return true; 495 } 496 if (this.failedAttempts.get() > FAILED_ATTEMPTS_THRESHOLD) { 497 // Don't try again 498 return false; 499 } 500 501 synchronized (this) { 502 if (this.compiledAst != null) { 503 // Compiled by another thread before this thread got into the sync block 504 return true; 505 } 506 SpelCompiler compiler = SpelCompiler.getCompiler(this.configuration.getCompilerClassLoader()); 507 compiledAst = compiler.compile(this.ast); 508 if (compiledAst != null) { 509 // Successfully compiled 510 this.compiledAst = compiledAst; 511 return true; 512 } 513 else { 514 // Failed to compile 515 this.failedAttempts.incrementAndGet(); 516 return false; 517 } 518 } 519 } 520 521 /** 522 * Cause an expression to revert to being interpreted if it has been using a compiled 523 * form. It also resets the compilation attempt failure count (an expression is normally no 524 * longer considered compilable if it cannot be compiled after 100 attempts). 525 */ 526 public void revertToInterpreted() { 527 this.compiledAst = null; 528 this.interpretedCount.set(0); 529 this.failedAttempts.set(0); 530 } 531 532 /** 533 * Return the Abstract Syntax Tree for the expression. 534 */ 535 public SpelNode getAST() { 536 return this.ast; 537 } 538 539 /** 540 * Produce a string representation of the Abstract Syntax Tree for the expression. 541 * This should ideally look like the input expression, but properly formatted since any 542 * unnecessary whitespace will have been discarded during the parse of the expression. 543 * @return the string representation of the AST 544 */ 545 public String toStringAST() { 546 return this.ast.toStringAST(); 547 } 548 549 private TypedValue toTypedValue(Object object) { 550 return (object != null ? new TypedValue(object) : TypedValue.NULL); 551 } 552 553}