/* * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.expression.spel.standard; import org.springframework.core.convert.TypeDescriptor; import org.springframework.expression.EvaluationContext; import org.springframework.expression.EvaluationException; import org.springframework.expression.Expression; import org.springframework.expression.TypedValue; import org.springframework.expression.common.ExpressionUtils; import org.springframework.expression.spel.CompiledExpression; import org.springframework.expression.spel.ExpressionState; import org.springframework.expression.spel.SpelCompilerMode; import org.springframework.expression.spel.SpelEvaluationException; import org.springframework.expression.spel.SpelMessage; import org.springframework.expression.spel.SpelNode; import org.springframework.expression.spel.SpelParserConfiguration; import org.springframework.expression.spel.ast.SpelNodeImpl; import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.util.Assert; /** * A {@code SpelExpression} represents a parsed (valid) expression that is ready to be * evaluated in a specified context. An expression can be evaluated standalone or in a * specified context. During expression evaluation the context may be asked to resolve * references to types, beans, properties, and methods. * * @author Andy Clement * @since 3.0 */ public class SpelExpression implements Expression { // Number of times to interpret an expression before compiling it private static final int INTERPRETED_COUNT_THRESHOLD = 100; // Number of times to try compiling an expression before giving up private static final int FAILED_ATTEMPTS_THRESHOLD = 100; private final String expression; private final SpelNodeImpl ast; private final SpelParserConfiguration configuration; // The default context is used if no override is supplied by the user private EvaluationContext evaluationContext; // Holds the compiled form of the expression (if it has been compiled) private CompiledExpression compiledAst; // Count of many times as the expression been interpreted - can trigger compilation // when certain limit reached private volatile int interpretedCount = 0; // The number of times compilation was attempted and failed - enables us to eventually // give up trying to compile it when it just doesn't seem to be possible. private volatile int failedAttempts = 0; /** * Construct an expression, only used by the parser. */ public SpelExpression(String expression, SpelNodeImpl ast, SpelParserConfiguration configuration) { this.expression = expression; this.ast = ast; this.configuration = configuration; } /** * Set the evaluation context that will be used if none is specified on an evaluation call. * @param evaluationContext the evaluation context to use */ public void setEvaluationContext(EvaluationContext evaluationContext) { this.evaluationContext = evaluationContext; } /** * Return the default evaluation context that will be used if none is supplied on an evaluation call. * @return the default evaluation context */ public EvaluationContext getEvaluationContext() { if (this.evaluationContext == null) { this.evaluationContext = new StandardEvaluationContext(); } return this.evaluationContext; } // implementing Expression @Override public Object getValue() throws EvaluationException { Object result; if (this.compiledAst != null) { try { TypedValue contextRoot = evaluationContext == null ? null : evaluationContext.getRootObject(); return this.compiledAst.getValue(contextRoot == null ? null : contextRoot.getValue(), evaluationContext); } catch (Throwable ex) { // If running in mixed mode, revert to interpreted if (this.configuration.getCompilerMode() == SpelCompilerMode.MIXED) { this.interpretedCount = 0; this.compiledAst = null; } else { // Running in SpelCompilerMode.immediate mode - propagate exception to caller throw new SpelEvaluationException(ex, SpelMessage.EXCEPTION_RUNNING_COMPILED_EXPRESSION); } } } ExpressionState expressionState = new ExpressionState(getEvaluationContext(), this.configuration); result = this.ast.getValue(expressionState); checkCompile(expressionState); return result; } @Override public Object getValue(Object rootObject) throws EvaluationException { Object result; if (this.compiledAst != null) { try { return this.compiledAst.getValue(rootObject, evaluationContext); } catch (Throwable ex) { // If running in mixed mode, revert to interpreted if (this.configuration.getCompilerMode() == SpelCompilerMode.MIXED) { this.interpretedCount = 0; this.compiledAst = null; } else { // Running in SpelCompilerMode.immediate mode - propagate exception to caller throw new SpelEvaluationException(ex, SpelMessage.EXCEPTION_RUNNING_COMPILED_EXPRESSION); } } } ExpressionState expressionState = new ExpressionState(getEvaluationContext(), toTypedValue(rootObject), this.configuration); result = this.ast.getValue(expressionState); checkCompile(expressionState); return result; } @SuppressWarnings("unchecked") @Override public <T> T getValue(Class<T> expectedResultType) throws EvaluationException { if (this.compiledAst != null) { try { TypedValue contextRoot = evaluationContext == null ? null : evaluationContext.getRootObject(); Object result = this.compiledAst.getValue(contextRoot == null ? null : contextRoot.getValue(), evaluationContext); if (expectedResultType == null) { return (T)result; } else { return ExpressionUtils.convertTypedValue(getEvaluationContext(), new TypedValue(result), expectedResultType); } } catch (Throwable ex) { // If running in mixed mode, revert to interpreted if (this.configuration.getCompilerMode() == SpelCompilerMode.MIXED) { this.interpretedCount = 0; this.compiledAst = null; } else { // Running in SpelCompilerMode.immediate mode - propagate exception to caller throw new SpelEvaluationException(ex, SpelMessage.EXCEPTION_RUNNING_COMPILED_EXPRESSION); } } } ExpressionState expressionState = new ExpressionState(getEvaluationContext(), this.configuration); TypedValue typedResultValue = this.ast.getTypedValue(expressionState); checkCompile(expressionState); return ExpressionUtils.convertTypedValue(expressionState.getEvaluationContext(), typedResultValue, expectedResultType); } @SuppressWarnings("unchecked") @Override public <T> T getValue(Object rootObject, Class<T> expectedResultType) throws EvaluationException { if (this.compiledAst != null) { try { Object result = this.compiledAst.getValue(rootObject, null); if (expectedResultType == null) { return (T)result; } else { return ExpressionUtils.convertTypedValue(getEvaluationContext(), new TypedValue(result), expectedResultType); } } catch (Throwable ex) { // If running in mixed mode, revert to interpreted if (this.configuration.getCompilerMode() == SpelCompilerMode.MIXED) { this.interpretedCount = 0; this.compiledAst = null; } else { // Running in SpelCompilerMode.immediate mode - propagate exception to caller throw new SpelEvaluationException(ex, SpelMessage.EXCEPTION_RUNNING_COMPILED_EXPRESSION); } } } ExpressionState expressionState = new ExpressionState(getEvaluationContext(), toTypedValue(rootObject), this.configuration); TypedValue typedResultValue = this.ast.getTypedValue(expressionState); checkCompile(expressionState); return ExpressionUtils.convertTypedValue(expressionState.getEvaluationContext(), typedResultValue, expectedResultType); } @Override public Object getValue(EvaluationContext context) throws EvaluationException { Assert.notNull(context, "EvaluationContext is required"); if (compiledAst!= null) { try { TypedValue contextRoot = context == null ? null : context.getRootObject(); return this.compiledAst.getValue(contextRoot != null ? contextRoot.getValue() : null, context); } catch (Throwable ex) { // If running in mixed mode, revert to interpreted if (this.configuration.getCompilerMode() == SpelCompilerMode.MIXED) { this.interpretedCount = 0; this.compiledAst = null; } else { // Running in SpelCompilerMode.immediate mode - propagate exception to caller throw new SpelEvaluationException(ex, SpelMessage.EXCEPTION_RUNNING_COMPILED_EXPRESSION); } } } ExpressionState expressionState = new ExpressionState(context, this.configuration); Object result = this.ast.getValue(expressionState); checkCompile(expressionState); return result; } @Override public Object getValue(EvaluationContext context, Object rootObject) throws EvaluationException { Assert.notNull(context, "EvaluationContext is required"); if (this.compiledAst != null) { try { return this.compiledAst.getValue(rootObject,context); } catch (Throwable ex) { // If running in mixed mode, revert to interpreted if (this.configuration.getCompilerMode() == SpelCompilerMode.MIXED) { this.interpretedCount = 0; this.compiledAst = null; } else { // Running in SpelCompilerMode.immediate mode - propagate exception to caller throw new SpelEvaluationException(ex, SpelMessage.EXCEPTION_RUNNING_COMPILED_EXPRESSION); } } } ExpressionState expressionState = new ExpressionState(context, toTypedValue(rootObject), this.configuration); Object result = this.ast.getValue(expressionState); checkCompile(expressionState); return result; } @SuppressWarnings("unchecked") @Override public <T> T getValue(EvaluationContext context, Class<T> expectedResultType) throws EvaluationException { if (this.compiledAst != null) { try { TypedValue contextRoot = context == null ? null : context.getRootObject(); Object result = this.compiledAst.getValue(contextRoot==null?null:contextRoot.getValue(),context); if (expectedResultType != null) { return ExpressionUtils.convertTypedValue(context, new TypedValue(result), expectedResultType); } else { return (T) result; } } catch (Throwable ex) { // If running in mixed mode, revert to interpreted if (this.configuration.getCompilerMode() == SpelCompilerMode.MIXED) { this.interpretedCount = 0; this.compiledAst = null; } else { // Running in SpelCompilerMode.immediate mode - propagate exception to caller throw new SpelEvaluationException(ex, SpelMessage.EXCEPTION_RUNNING_COMPILED_EXPRESSION); } } } ExpressionState expressionState = new ExpressionState(context, this.configuration); TypedValue typedResultValue = this.ast.getTypedValue(expressionState); checkCompile(expressionState); return ExpressionUtils.convertTypedValue(context, typedResultValue, expectedResultType); } @SuppressWarnings("unchecked") @Override public <T> T getValue(EvaluationContext context, Object rootObject, Class<T> expectedResultType) throws EvaluationException { if (this.compiledAst != null) { try { Object result = this.compiledAst.getValue(rootObject,context); if (expectedResultType != null) { return ExpressionUtils.convertTypedValue(context, new TypedValue(result), expectedResultType); } else { return (T) result; } } catch (Throwable ex) { // If running in mixed mode, revert to interpreted if (this.configuration.getCompilerMode() == SpelCompilerMode.MIXED) { this.interpretedCount = 0; this.compiledAst = null; } else { // Running in SpelCompilerMode.immediate mode - propagate exception to caller throw new SpelEvaluationException(ex, SpelMessage.EXCEPTION_RUNNING_COMPILED_EXPRESSION); } } } ExpressionState expressionState = new ExpressionState(context, toTypedValue(rootObject), this.configuration); TypedValue typedResultValue = this.ast.getTypedValue(expressionState); checkCompile(expressionState); return ExpressionUtils.convertTypedValue(context, typedResultValue, expectedResultType); } @Override public Class<?> getValueType() throws EvaluationException { return getValueType(getEvaluationContext()); } @Override public Class<?> getValueType(Object rootObject) throws EvaluationException { return getValueType(getEvaluationContext(), rootObject); } @Override public Class<?> getValueType(EvaluationContext context) throws EvaluationException { Assert.notNull(context, "EvaluationContext is required"); ExpressionState expressionState = new ExpressionState(context, this.configuration); TypeDescriptor typeDescriptor = this.ast.getValueInternal(expressionState).getTypeDescriptor(); return (typeDescriptor != null ? typeDescriptor.getType() : null); } @Override public Class<?> getValueType(EvaluationContext context, Object rootObject) throws EvaluationException { ExpressionState expressionState = new ExpressionState(context, toTypedValue(rootObject), this.configuration); TypeDescriptor typeDescriptor = this.ast.getValueInternal(expressionState).getTypeDescriptor(); return (typeDescriptor != null ? typeDescriptor.getType() : null); } @Override public TypeDescriptor getValueTypeDescriptor() throws EvaluationException { return getValueTypeDescriptor(getEvaluationContext()); } @Override public TypeDescriptor getValueTypeDescriptor(Object rootObject) throws EvaluationException { ExpressionState expressionState = new ExpressionState(getEvaluationContext(), toTypedValue(rootObject), this.configuration); return this.ast.getValueInternal(expressionState).getTypeDescriptor(); } @Override public TypeDescriptor getValueTypeDescriptor(EvaluationContext context) throws EvaluationException { Assert.notNull(context, "EvaluationContext is required"); ExpressionState expressionState = new ExpressionState(context, this.configuration); return this.ast.getValueInternal(expressionState).getTypeDescriptor(); } @Override public TypeDescriptor getValueTypeDescriptor(EvaluationContext context, Object rootObject) throws EvaluationException { Assert.notNull(context, "EvaluationContext is required"); ExpressionState expressionState = new ExpressionState(context, toTypedValue(rootObject), this.configuration); return this.ast.getValueInternal(expressionState).getTypeDescriptor(); } @Override public String getExpressionString() { return this.expression; } @Override public boolean isWritable(EvaluationContext context) throws EvaluationException { Assert.notNull(context, "EvaluationContext is required"); return this.ast.isWritable(new ExpressionState(context, this.configuration)); } @Override public boolean isWritable(Object rootObject) throws EvaluationException { return this.ast.isWritable(new ExpressionState(getEvaluationContext(), toTypedValue(rootObject), this.configuration)); } @Override public boolean isWritable(EvaluationContext context, Object rootObject) throws EvaluationException { Assert.notNull(context, "EvaluationContext is required"); return this.ast.isWritable(new ExpressionState(context, toTypedValue(rootObject), this.configuration)); } @Override public void setValue(EvaluationContext context, Object value) throws EvaluationException { Assert.notNull(context, "EvaluationContext is required"); this.ast.setValue(new ExpressionState(context, this.configuration), value); } @Override public void setValue(Object rootObject, Object value) throws EvaluationException { this.ast.setValue(new ExpressionState(getEvaluationContext(), toTypedValue(rootObject), this.configuration), value); } @Override public void setValue(EvaluationContext context, Object rootObject, Object value) throws EvaluationException { Assert.notNull(context, "EvaluationContext is required"); this.ast.setValue(new ExpressionState(context, toTypedValue(rootObject), this.configuration), value); } /** * Compile the expression if it has been evaluated more than the threshold number * of times to trigger compilation. * @param expressionState the expression state used to determine compilation mode */ private void checkCompile(ExpressionState expressionState) { this.interpretedCount++; SpelCompilerMode compilerMode = expressionState.getConfiguration().getCompilerMode(); if (compilerMode != SpelCompilerMode.OFF) { if (compilerMode == SpelCompilerMode.IMMEDIATE) { if (this.interpretedCount > 1) { compileExpression(); } } else { // compilerMode = SpelCompilerMode.MIXED if (this.interpretedCount > INTERPRETED_COUNT_THRESHOLD) { compileExpression(); } } } } /** * Perform expression compilation. This will only succeed once exit descriptors for all nodes have * been determined. If the compilation fails and has failed more than 100 times the expression is * no longer considered suitable for compilation. */ public boolean compileExpression() { if (this.failedAttempts > FAILED_ATTEMPTS_THRESHOLD) { // Don't try again return false; } if (this.compiledAst == null) { synchronized (this.expression) { // Possibly compiled by another thread before this thread got into the sync block if (this.compiledAst != null) { return true; } SpelCompiler compiler = SpelCompiler.getCompiler(this.configuration.getCompilerClassLoader()); this.compiledAst = compiler.compile(this.ast); if (this.compiledAst == null) { this.failedAttempts++; } } } return (this.compiledAst != null); } /** * Cause an expression to revert to being interpreted if it has been using a compiled * form. It also resets the compilation attempt failure count (an expression is normally no * longer considered compilable if it cannot be compiled after 100 attempts). */ public void revertToInterpreted() { this.compiledAst = null; this.interpretedCount = 0; this.failedAttempts = 0; } /** * Return the Abstract Syntax Tree for the expression. */ public SpelNode getAST() { return this.ast; } /** * Produce a string representation of the Abstract Syntax Tree for the expression. * This should ideally look like the input expression, but properly formatted since any * unnecessary whitespace will have been discarded during the parse of the expression. * @return the string representation of the AST */ public String toStringAST() { return this.ast.toStringAST(); } private TypedValue toTypedValue(Object object) { if (object == null) { return TypedValue.NULL; } else { return new TypedValue(object); } } }