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}