View Javadoc
1   /*
2    * Copyright 2002-2014 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.expression.spel.standard;
18  
19  import org.springframework.core.convert.TypeDescriptor;
20  import org.springframework.expression.EvaluationContext;
21  import org.springframework.expression.EvaluationException;
22  import org.springframework.expression.Expression;
23  import org.springframework.expression.TypedValue;
24  import org.springframework.expression.common.ExpressionUtils;
25  import org.springframework.expression.spel.CompiledExpression;
26  import org.springframework.expression.spel.ExpressionState;
27  import org.springframework.expression.spel.SpelCompilerMode;
28  import org.springframework.expression.spel.SpelEvaluationException;
29  import org.springframework.expression.spel.SpelMessage;
30  import org.springframework.expression.spel.SpelNode;
31  import org.springframework.expression.spel.SpelParserConfiguration;
32  import org.springframework.expression.spel.ast.SpelNodeImpl;
33  import org.springframework.expression.spel.support.StandardEvaluationContext;
34  import org.springframework.util.Assert;
35  
36  /**
37   * A {@code SpelExpression} represents a parsed (valid) expression that is ready to be
38   * evaluated in a specified context. An expression can be evaluated standalone or in a
39   * specified context. During expression evaluation the context may be asked to resolve
40   * references to types, beans, properties, and methods.
41   *
42   * @author Andy Clement
43   * @since 3.0
44   */
45  public class SpelExpression implements Expression {
46  
47  	// Number of times to interpret an expression before compiling it
48  	private static final int INTERPRETED_COUNT_THRESHOLD = 100;
49  
50  	// Number of times to try compiling an expression before giving up
51  	private static final int FAILED_ATTEMPTS_THRESHOLD = 100;
52  
53  
54  	private final String expression;
55  
56  	private final SpelNodeImpl ast;
57  
58  	private final SpelParserConfiguration configuration;
59  
60  	// The default context is used if no override is supplied by the user
61  	private EvaluationContext evaluationContext;
62  
63  	// Holds the compiled form of the expression (if it has been compiled)
64  	private CompiledExpression compiledAst;
65  
66  	// Count of many times as the expression been interpreted - can trigger compilation
67  	// when certain limit reached
68  	private volatile int interpretedCount = 0;
69  	
70  	// The number of times compilation was attempted and failed - enables us to eventually
71  	// give up trying to compile it when it just doesn't seem to be possible.
72  	private volatile int failedAttempts = 0;
73  
74  
75  	/**
76  	 * Construct an expression, only used by the parser.
77  	 */
78  	public SpelExpression(String expression, SpelNodeImpl ast, SpelParserConfiguration configuration) {
79  		this.expression = expression;
80  		this.ast = ast;
81  		this.configuration = configuration;
82  	}
83  
84  
85  	/**
86  	 * Set the evaluation context that will be used if none is specified on an evaluation call.
87  	 * @param evaluationContext the evaluation context to use
88  	 */
89  	public void setEvaluationContext(EvaluationContext evaluationContext) {
90  		this.evaluationContext = evaluationContext;
91  	}
92  
93  	/**
94  	 * Return the default evaluation context that will be used if none is supplied on an evaluation call.
95  	 * @return the default evaluation context
96  	 */
97  	public EvaluationContext getEvaluationContext() {
98  		if (this.evaluationContext == null) {
99  			this.evaluationContext = new StandardEvaluationContext();
100 		}
101 		return this.evaluationContext;
102 	}
103 
104 
105 	// implementing Expression
106 
107 	@Override
108 	public Object getValue() throws EvaluationException {
109 		Object result;
110 		if (this.compiledAst != null) {
111 			try {
112 				TypedValue contextRoot = evaluationContext == null ? null : evaluationContext.getRootObject();
113 				return this.compiledAst.getValue(contextRoot == null ? null : contextRoot.getValue(), evaluationContext);
114 			}
115 			catch (Throwable ex) {
116 				// If running in mixed mode, revert to interpreted
117 				if (this.configuration.getCompilerMode() == SpelCompilerMode.MIXED) {
118 					this.interpretedCount = 0;
119 					this.compiledAst = null;
120 				}
121 				else {
122 					// Running in SpelCompilerMode.immediate mode - propagate exception to caller
123 					throw new SpelEvaluationException(ex, SpelMessage.EXCEPTION_RUNNING_COMPILED_EXPRESSION);
124 				}
125 			}
126 		}
127 		ExpressionState expressionState = new ExpressionState(getEvaluationContext(), this.configuration);
128 		result = this.ast.getValue(expressionState);
129 		checkCompile(expressionState);
130 		return result;
131 	}
132 
133 	@Override
134 	public Object getValue(Object rootObject) throws EvaluationException {
135 		Object result;
136 		if (this.compiledAst != null) {
137 			try {
138 				return this.compiledAst.getValue(rootObject, evaluationContext);
139 			}
140 			catch (Throwable ex) {
141 				// If running in mixed mode, revert to interpreted
142 				if (this.configuration.getCompilerMode() == SpelCompilerMode.MIXED) {
143 					this.interpretedCount = 0;
144 					this.compiledAst = null;
145 				}
146 				else {
147 					// Running in SpelCompilerMode.immediate mode - propagate exception to caller
148 					throw new SpelEvaluationException(ex, SpelMessage.EXCEPTION_RUNNING_COMPILED_EXPRESSION);
149 				}
150 			}
151 		}
152 		ExpressionState expressionState = new ExpressionState(getEvaluationContext(), toTypedValue(rootObject), this.configuration);
153 		result = this.ast.getValue(expressionState);
154 		checkCompile(expressionState);
155 		return result;
156 	}
157 
158 	@SuppressWarnings("unchecked")
159 	@Override
160 	public <T> T getValue(Class<T> expectedResultType) throws EvaluationException {
161 		if (this.compiledAst != null) {
162 			try {
163 				TypedValue contextRoot = evaluationContext == null ? null : evaluationContext.getRootObject();
164 				Object result = this.compiledAst.getValue(contextRoot == null ? null : contextRoot.getValue(), evaluationContext);
165 				if (expectedResultType == null) {
166 					return (T)result;
167 				}
168 				else {
169 					return ExpressionUtils.convertTypedValue(getEvaluationContext(), new TypedValue(result), expectedResultType);
170 				}
171 			}
172 			catch (Throwable ex) {
173 				// If running in mixed mode, revert to interpreted
174 				if (this.configuration.getCompilerMode() == SpelCompilerMode.MIXED) {
175 					this.interpretedCount = 0;
176 					this.compiledAst = null;
177 				}
178 				else {
179 					// Running in SpelCompilerMode.immediate mode - propagate exception to caller
180 					throw new SpelEvaluationException(ex, SpelMessage.EXCEPTION_RUNNING_COMPILED_EXPRESSION);
181 				}
182 			}
183 		}
184 		ExpressionState expressionState = new ExpressionState(getEvaluationContext(), this.configuration);
185 		TypedValue typedResultValue = this.ast.getTypedValue(expressionState);
186 		checkCompile(expressionState);
187 		return ExpressionUtils.convertTypedValue(expressionState.getEvaluationContext(), typedResultValue, expectedResultType);
188 	}
189 
190 	@SuppressWarnings("unchecked")
191 	@Override
192 	public <T> T getValue(Object rootObject, Class<T> expectedResultType) throws EvaluationException {
193 		if (this.compiledAst != null) {
194 			try {
195 				Object result = this.compiledAst.getValue(rootObject, null);
196 				if (expectedResultType == null) {
197 					return (T)result;
198 				}
199 				else {
200 					return ExpressionUtils.convertTypedValue(getEvaluationContext(), new TypedValue(result), expectedResultType);
201 				}
202 			}
203 			catch (Throwable ex) {
204 				// If running in mixed mode, revert to interpreted
205 				if (this.configuration.getCompilerMode() == SpelCompilerMode.MIXED) {
206 					this.interpretedCount = 0;
207 					this.compiledAst = null;
208 				}
209 				else {
210 					// Running in SpelCompilerMode.immediate mode - propagate exception to caller
211 					throw new SpelEvaluationException(ex, SpelMessage.EXCEPTION_RUNNING_COMPILED_EXPRESSION);
212 				}
213 			}
214 		}
215 		ExpressionState expressionState = new ExpressionState(getEvaluationContext(), toTypedValue(rootObject), this.configuration);
216 		TypedValue typedResultValue = this.ast.getTypedValue(expressionState);
217 		checkCompile(expressionState);
218 		return ExpressionUtils.convertTypedValue(expressionState.getEvaluationContext(), typedResultValue, expectedResultType);
219 	}
220 
221 	@Override
222 	public Object getValue(EvaluationContext context) throws EvaluationException {
223 		Assert.notNull(context, "EvaluationContext is required");
224 		if (compiledAst!= null) {
225 			try {
226 				TypedValue contextRoot = context == null ? null : context.getRootObject();
227 				return this.compiledAst.getValue(contextRoot != null ? contextRoot.getValue() : null, context);
228 			}
229 			catch (Throwable ex) {
230 				// If running in mixed mode, revert to interpreted
231 				if (this.configuration.getCompilerMode() == SpelCompilerMode.MIXED) {
232 					this.interpretedCount = 0;
233 					this.compiledAst = null;
234 				}
235 				else {
236 					// Running in SpelCompilerMode.immediate mode - propagate exception to caller
237 					throw new SpelEvaluationException(ex, SpelMessage.EXCEPTION_RUNNING_COMPILED_EXPRESSION);
238 				}
239 			}
240 		}
241 		ExpressionState expressionState = new ExpressionState(context, this.configuration);
242 		Object result = this.ast.getValue(expressionState);
243 		checkCompile(expressionState);
244 		return result;
245 	}
246 
247 	@Override
248 	public Object getValue(EvaluationContext context, Object rootObject) throws EvaluationException {
249 		Assert.notNull(context, "EvaluationContext is required");
250 		if (this.compiledAst != null) {
251 			try {
252 				return this.compiledAst.getValue(rootObject,context);
253 			}
254 			catch (Throwable ex) {
255 				// If running in mixed mode, revert to interpreted
256 				if (this.configuration.getCompilerMode() == SpelCompilerMode.MIXED) {
257 					this.interpretedCount = 0;
258 					this.compiledAst = null;
259 				}
260 				else {
261 					// Running in SpelCompilerMode.immediate mode - propagate exception to caller
262 					throw new SpelEvaluationException(ex, SpelMessage.EXCEPTION_RUNNING_COMPILED_EXPRESSION);
263 				}
264 			}
265 		}
266 		ExpressionState expressionState = new ExpressionState(context, toTypedValue(rootObject), this.configuration);
267 		Object result = this.ast.getValue(expressionState);
268 		checkCompile(expressionState);
269 		return result;
270 	}
271 
272 	@SuppressWarnings("unchecked")
273 	@Override
274 	public <T> T getValue(EvaluationContext context, Class<T> expectedResultType) throws EvaluationException {
275 		if (this.compiledAst != null) {
276 			try {
277 				TypedValue contextRoot = context == null ? null : context.getRootObject();
278 				Object result = this.compiledAst.getValue(contextRoot==null?null:contextRoot.getValue(),context);
279 				if (expectedResultType != null) {
280 					return ExpressionUtils.convertTypedValue(context, new TypedValue(result), expectedResultType);
281 				}
282 				else {
283 					return (T) result;
284 				}
285 			}
286 			catch (Throwable ex) {
287 				// If running in mixed mode, revert to interpreted
288 				if (this.configuration.getCompilerMode() == SpelCompilerMode.MIXED) {
289 					this.interpretedCount = 0;
290 					this.compiledAst = null;
291 				}
292 				else {
293 					// Running in SpelCompilerMode.immediate mode - propagate exception to caller
294 					throw new SpelEvaluationException(ex, SpelMessage.EXCEPTION_RUNNING_COMPILED_EXPRESSION);
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 	@SuppressWarnings("unchecked")
305 	@Override
306 	public <T> T getValue(EvaluationContext context, Object rootObject, Class<T> expectedResultType) throws EvaluationException {
307 		if (this.compiledAst != null) {
308 			try {
309 				Object result = this.compiledAst.getValue(rootObject,context);
310 				if (expectedResultType != null) {
311 					return ExpressionUtils.convertTypedValue(context, new TypedValue(result), expectedResultType);
312 				}
313 				else {
314 					return (T) result;
315 				}
316 			}
317 			catch (Throwable ex) {
318 				// If running in mixed mode, revert to interpreted
319 				if (this.configuration.getCompilerMode() == SpelCompilerMode.MIXED) {
320 					this.interpretedCount = 0;
321 					this.compiledAst = null;
322 				}
323 				else {
324 					// Running in SpelCompilerMode.immediate mode - propagate exception to caller
325 					throw new SpelEvaluationException(ex, SpelMessage.EXCEPTION_RUNNING_COMPILED_EXPRESSION);
326 				}
327 			}
328 		}
329 		ExpressionState expressionState = new ExpressionState(context, toTypedValue(rootObject), this.configuration);
330 		TypedValue typedResultValue = this.ast.getTypedValue(expressionState);
331 		checkCompile(expressionState);
332 		return ExpressionUtils.convertTypedValue(context, typedResultValue, expectedResultType);
333 	}
334 
335 	@Override
336 	public Class<?> getValueType() throws EvaluationException {
337 		return getValueType(getEvaluationContext());
338 	}
339 
340 	@Override
341 	public Class<?> getValueType(Object rootObject) throws EvaluationException {
342 		return getValueType(getEvaluationContext(), rootObject);
343 	}
344 
345 	@Override
346 	public Class<?> getValueType(EvaluationContext context) throws EvaluationException {
347 		Assert.notNull(context, "EvaluationContext is required");
348 		ExpressionState expressionState = new ExpressionState(context, this.configuration);
349 		TypeDescriptor typeDescriptor = this.ast.getValueInternal(expressionState).getTypeDescriptor();
350 		return (typeDescriptor != null ? typeDescriptor.getType() : null);
351 	}
352 
353 	@Override
354 	public Class<?> getValueType(EvaluationContext context, Object rootObject) throws EvaluationException {
355 		ExpressionState expressionState = new ExpressionState(context, toTypedValue(rootObject), this.configuration);
356 		TypeDescriptor typeDescriptor = this.ast.getValueInternal(expressionState).getTypeDescriptor();
357 		return (typeDescriptor != null ? typeDescriptor.getType() : null);
358 	}
359 
360 	@Override
361 	public TypeDescriptor getValueTypeDescriptor() throws EvaluationException {
362 		return getValueTypeDescriptor(getEvaluationContext());
363 	}
364 
365 	@Override
366 	public TypeDescriptor getValueTypeDescriptor(Object rootObject) throws EvaluationException {
367 		ExpressionState expressionState =
368 				new ExpressionState(getEvaluationContext(), toTypedValue(rootObject), this.configuration);
369 		return this.ast.getValueInternal(expressionState).getTypeDescriptor();
370 	}
371 
372 	@Override
373 	public TypeDescriptor getValueTypeDescriptor(EvaluationContext context) throws EvaluationException {
374 		Assert.notNull(context, "EvaluationContext is required");
375 		ExpressionState expressionState = new ExpressionState(context, this.configuration);
376 		return this.ast.getValueInternal(expressionState).getTypeDescriptor();
377 	}
378 
379 	@Override
380 	public TypeDescriptor getValueTypeDescriptor(EvaluationContext context, Object rootObject) throws EvaluationException {
381 		Assert.notNull(context, "EvaluationContext is required");
382 		ExpressionState expressionState = new ExpressionState(context, toTypedValue(rootObject), this.configuration);
383 		return this.ast.getValueInternal(expressionState).getTypeDescriptor();
384 	}
385 
386 	@Override
387 	public String getExpressionString() {
388 		return this.expression;
389 	}
390 
391 	@Override
392 	public boolean isWritable(EvaluationContext context) throws EvaluationException {
393 		Assert.notNull(context, "EvaluationContext is required");
394 		return this.ast.isWritable(new ExpressionState(context, this.configuration));
395 	}
396 
397 	@Override
398 	public boolean isWritable(Object rootObject) throws EvaluationException {
399 		return this.ast.isWritable(new ExpressionState(getEvaluationContext(), toTypedValue(rootObject), this.configuration));
400 	}
401 
402 	@Override
403 	public boolean isWritable(EvaluationContext context, Object rootObject) throws EvaluationException {
404 		Assert.notNull(context, "EvaluationContext is required");
405 		return this.ast.isWritable(new ExpressionState(context, toTypedValue(rootObject), this.configuration));
406 	}
407 
408 	@Override
409 	public void setValue(EvaluationContext context, Object value) throws EvaluationException {
410 		Assert.notNull(context, "EvaluationContext is required");
411 		this.ast.setValue(new ExpressionState(context, this.configuration), value);
412 	}
413 
414 	@Override
415 	public void setValue(Object rootObject, Object value) throws EvaluationException {
416 		this.ast.setValue(new ExpressionState(getEvaluationContext(), toTypedValue(rootObject), this.configuration), value);
417 	}
418 
419 	@Override
420 	public void setValue(EvaluationContext context, Object rootObject, Object value) throws EvaluationException {
421 		Assert.notNull(context, "EvaluationContext is required");
422 		this.ast.setValue(new ExpressionState(context, toTypedValue(rootObject), this.configuration), value);
423 	}
424 
425 
426 	/**
427 	 * Compile the expression if it has been evaluated more than the threshold number
428 	 * of times to trigger compilation.
429 	 * @param expressionState the expression state used to determine compilation mode
430 	 */
431 	private void checkCompile(ExpressionState expressionState) {
432 		this.interpretedCount++;
433 		SpelCompilerMode compilerMode = expressionState.getConfiguration().getCompilerMode();
434 		if (compilerMode != SpelCompilerMode.OFF) {
435 			if (compilerMode == SpelCompilerMode.IMMEDIATE) {
436 				if (this.interpretedCount > 1) {
437 					compileExpression();
438 				}
439 			}
440 			else {
441 				// compilerMode = SpelCompilerMode.MIXED
442 				if (this.interpretedCount > INTERPRETED_COUNT_THRESHOLD) {
443 					compileExpression();
444 				}
445 			}
446 		}
447 	}
448 
449 
450 	/**
451 	 * Perform expression compilation. This will only succeed once exit descriptors for all nodes have 
452 	 * been determined. If the compilation fails and has failed more than 100 times the expression is 
453 	 * no longer considered suitable for compilation.
454 	 */
455 	public boolean compileExpression() {
456 		if (this.failedAttempts > FAILED_ATTEMPTS_THRESHOLD) {
457 			// Don't try again
458 			return false;
459 		}
460 		if (this.compiledAst == null) {
461 			synchronized (this.expression) {
462 				// Possibly compiled by another thread before this thread got into the sync block
463 				if (this.compiledAst != null) {
464 					return true;
465 				}
466 				SpelCompiler compiler = SpelCompiler.getCompiler(this.configuration.getCompilerClassLoader());
467 				this.compiledAst = compiler.compile(this.ast);
468 				if (this.compiledAst == null) {
469 					this.failedAttempts++;
470 				}
471 			}
472 		}
473 		return (this.compiledAst != null);
474 	}
475 
476 	/**
477 	 * Cause an expression to revert to being interpreted if it has been using a compiled
478 	 * form. It also resets the compilation attempt failure count (an expression is normally no
479 	 * longer considered compilable if it cannot be compiled after 100 attempts).
480 	 */
481 	public void revertToInterpreted() {
482 		this.compiledAst = null;
483 		this.interpretedCount = 0;
484 		this.failedAttempts = 0;
485 	}
486 
487 	/**
488 	 * Return the Abstract Syntax Tree for the expression.
489 	 */
490 	public SpelNode getAST() {
491 		return this.ast;
492 	}
493 
494 	/**
495 	 * Produce a string representation of the Abstract Syntax Tree for the expression.
496 	 * This should ideally look like the input expression, but properly formatted since any
497 	 * unnecessary whitespace will have been discarded during the parse of the expression.
498 	 * @return the string representation of the AST
499 	 */
500 	public String toStringAST() {
501 		return this.ast.toStringAST();
502 	}
503 
504 	private TypedValue toTypedValue(Object object) {
505 		if (object == null) {
506 			return TypedValue.NULL;
507 		}
508 		else {
509 			return new TypedValue(object);
510 		}
511 	}
512 
513 }