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}