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