From 4bc3e0c2b4c703b3da90774e948f8adb18f334f9 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Wed, 14 Mar 2018 16:10:28 -0400 Subject: [PATCH] Add SimpleEvaluationContext Issue: SPR-16588 --- .../spel/support/SimpleEvaluationContext.java | 195 ++++++++++++++++ src/asciidoc/core-expressions.adoc | 214 ++++++++---------- 2 files changed, 294 insertions(+), 115 deletions(-) create mode 100644 spring-expression/src/main/java/org/springframework/expression/spel/support/SimpleEvaluationContext.java diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/support/SimpleEvaluationContext.java b/spring-expression/src/main/java/org/springframework/expression/spel/support/SimpleEvaluationContext.java new file mode 100644 index 0000000000..06f14cc8f0 --- /dev/null +++ b/spring-expression/src/main/java/org/springframework/expression/spel/support/SimpleEvaluationContext.java @@ -0,0 +1,195 @@ +/* + * Copyright 2002-2018 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.support; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.springframework.expression.BeanResolver; +import org.springframework.expression.ConstructorResolver; +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.EvaluationException; +import org.springframework.expression.MethodResolver; +import org.springframework.expression.OperatorOverloader; +import org.springframework.expression.PropertyAccessor; +import org.springframework.expression.TypeComparator; +import org.springframework.expression.TypeConverter; +import org.springframework.expression.TypeLocator; +import org.springframework.expression.TypedValue; +import org.springframework.expression.spel.SpelEvaluationException; +import org.springframework.expression.spel.SpelMessage; + +/** + * A basic implementation of {@link EvaluationContext} that focuses on a subset + * of essential SpEL features and configuration options, and relies on default + * strategies otherwise. + * + *

In many cases, the full extent of the SpEL is not + * required and should be meaningfully restricted. Examples include but are not + * limited to data binding expressions, property-based filters, and others. To + * that effect, {@code SimpleEvaluationContext} supports only a subset of the + * SpEL language syntax that excludes references to Java types, constructors, + * and bean references. + * + *

Note that {@code SimpleEvaluationContext} cannot be configured with a + * default root object. Instead it is meant to be created once and used + * repeatedly through method variants on + * {@link org.springframework.expression.Expression Expression} that accept + * both an {@code EvaluationContext} and a root object. + * + * @author Rossen Stoyanchev + * @since 4.3.15 + */ +public class SimpleEvaluationContext implements EvaluationContext { + + private static final TypeLocator typeNotFoundTypeLocator = new TypeLocator() { + + @Override + public Class findType(String typeName) throws EvaluationException { + throw new SpelEvaluationException(SpelMessage.TYPE_NOT_FOUND, typeName); + } + }; + + + private final List propertyAccessors; + + private final List constructorResolvers = + Collections.singletonList(new ReflectiveConstructorResolver()); + + private final List methodResolvers = + Collections.singletonList(new ReflectiveMethodResolver()); + + private final TypeConverter typeConverter; + + private final TypeComparator typeComparator = new StandardTypeComparator(); + + private final OperatorOverloader operatorOverloader = new StandardOperatorOverloader(); + + private final Map variables = new HashMap(); + + + public SimpleEvaluationContext() { + this(null, null); + } + + public SimpleEvaluationContext(List accessors, TypeConverter converter) { + this.propertyAccessors = initPropertyAccessors(accessors); + this.typeConverter = converter != null ? converter : new StandardTypeConverter(); + } + + + private static List initPropertyAccessors(List accessors) { + if (accessors == null) { + accessors = new ArrayList(5); + accessors.add(new ReflectivePropertyAccessor()); + } + return accessors; + } + + + /** + * {@code SimpleEvaluationContext} cannot be configured with a root object. + * It is meant for repeated use with + * {@link org.springframework.expression.Expression Expression} method + * variants that accept both an {@code EvaluationContext} and a root object. + * @return Always returns {@link TypedValue#NULL}. + */ + @Override + public TypedValue getRootObject() { + return TypedValue.NULL; + } + + @Override + public List getPropertyAccessors() { + return this.propertyAccessors; + } + + /** + * Return a single {@link ReflectiveConstructorResolver}. + */ + @Override + public List getConstructorResolvers() { + return this.constructorResolvers; + } + + /** + * Return a single {@link ReflectiveMethodResolver}. + */ + @Override + public List getMethodResolvers() { + return this.methodResolvers; + } + + /** + * {@code SimpleEvaluationContext} does not support use of bean references. + * @return Always returns {@code null} + */ + @Override + public BeanResolver getBeanResolver() { + return null; + } + + /** + * {@code SimpleEvaluationContext} does not support use of type references. + * @return {@code TypeLocator} implementation that raises a + * {@link SpelEvaluationException} with {@link SpelMessage#TYPE_NOT_FOUND}. + */ + @Override + public TypeLocator getTypeLocator() { + return typeNotFoundTypeLocator; + } + + /** + * The configured {@link TypeConverter}. + *

By default this is {@link StandardTypeConverter}. + */ + @Override + public TypeConverter getTypeConverter() { + return this.typeConverter; + } + + /** + * Return an instance of {@link StandardTypeComparator}. + */ + @Override + public TypeComparator getTypeComparator() { + return this.typeComparator; + } + + + /** + * Return an instance of {@link StandardOperatorOverloader}. + */ + @Override + public OperatorOverloader getOperatorOverloader() { + return this.operatorOverloader; + } + + @Override + public void setVariable(String name, Object value) { + this.variables.put(name, value); + } + + @Override + public Object lookupVariable(String name) { + return this.variables.get(name); + } + +} diff --git a/src/asciidoc/core-expressions.adoc b/src/asciidoc/core-expressions.adoc index 85796bab87..3ac3a0e69c 100644 --- a/src/asciidoc/core-expressions.adoc +++ b/src/asciidoc/core-expressions.adoc @@ -6,6 +6,7 @@ [[expressions-intro]] == Introduction + The Spring Expression Language (SpEL for short) is a powerful expression language that supports querying and manipulating an object graph at runtime. The language syntax is similar to Unified EL but offers additional features, most notably method invocation and @@ -35,12 +36,7 @@ syntax. In several places an Inventor and Inventor's Society class are used as t target objects for expression evaluation. These class declarations and the data used to populate them are listed at the end of the chapter. - - - -[[expressions-features]] -== Feature Overview -The expression language supports the following functionality +The expression language supports the following functionality: * Literal expressions * Boolean and relational operators @@ -66,7 +62,8 @@ The expression language supports the following functionality [[expressions-evaluation]] -== Expression Evaluation using Spring's Expression Interface +== Evaluation + This section introduces the simple use of SpEL interfaces and its expression language. The complete language reference can be found in the section <>. @@ -153,10 +150,9 @@ result type. An `EvaluationException` will be thrown if the value cannot be cast type `T` or converted using the registered type converter. The more common usage of SpEL is to provide an expression string that is evaluated -against a specific object instance (called the root object). There are two options here -and which to choose depends on whether the object against which the expression is being -evaluated will be changing with each call to evaluate the expression. In the following -example we retrieve the `name` property from an instance of the Inventor class. +against a specific object instance (called the root object). The example shows +how to retrieve the `name` property from an instance of the `Inventor` class or +create a boolean condition: [source,java,indent=0] [subs="verbatim,quotes"] @@ -169,91 +165,43 @@ example we retrieve the `name` property from an instance of the Inventor class. Inventor tesla = new Inventor("Nikola Tesla", c.getTime(), "Serbian"); ExpressionParser parser = new SpelExpressionParser(); - Expression exp = parser.parseExpression("**name**"); - EvaluationContext context = new StandardEvaluationContext(tesla); - String name = (String) exp.getValue(context); ----- - -In the last line, the value of the string variable `name` will be set to "Nikola Tesla". -The class StandardEvaluationContext is where you can specify which object the "name" -property will be evaluated against. This is the mechanism to use if the root object is -unlikely to change, it can simply be set once in the evaluation context. If the root -object is likely to change repeatedly, it can be supplied on each call to `getValue`, as -this next example shows: - -[source,java,indent=0] -[subs="verbatim,quotes"] ----- - / Create and set a calendar - GregorianCalendar c = new GregorianCalendar(); - c.set(1856, 7, 9); - - // The constructor arguments are name, birthday, and nationality. - Inventor tesla = new Inventor("Nikola Tesla", c.getTime(), "Serbian"); - - ExpressionParser parser = new SpelExpressionParser(); Expression exp = parser.parseExpression("**name**"); String name = (String) exp.getValue(tesla); ----- + // name == "Nikola Tesla" -In this case the inventor `tesla` has been supplied directly to `getValue` and the -expression evaluation infrastructure creates and manages a default evaluation context -internally - it did not require one to be supplied. - -The StandardEvaluationContext is relatively expensive to construct and during repeated -usage it builds up cached state that enables subsequent expression evaluations to be -performed more quickly. For this reason it is better to cache and reuse them where -possible, rather than construct a new one for each expression evaluation. - -In some cases it can be desirable to use a configured evaluation context and yet still -supply a different root object on each call to `getValue`. `getValue` allows both to be -specified on the same call. In these situations the root object passed on the call is -considered to override any (which maybe null) specified on the evaluation context. - -[NOTE] -==== -In standalone usage of SpEL there is a need to create the parser, parse expressions and -perhaps provide evaluation contexts and a root context object. However, more common -usage is to provide only the SpEL expression string as part of a configuration file, for -example for Spring bean or Spring Web Flow definitions. In this case, the parser, -evaluation context, root object and any predefined variables are all set up implicitly, -requiring the user to specify nothing other than the expressions. -==== - -As a final introductory example, the use of a boolean operator is shown using the -Inventor object in the previous example. - -[source,java,indent=0] -[subs="verbatim,quotes"] ----- - Expression exp = parser.parseExpression("name == 'Nikola Tesla'"); - boolean result = exp.getValue(context, Boolean.class); // evaluates to true + exp = parser.parseExpression("name == 'Nikola Tesla'"); + boolean result = exp.getValue(tesla, Boolean.class); + // result == true ---- [[expressions-evaluation-context]] -=== The EvaluationContext interface +=== `EvaluationContext` + The interface `EvaluationContext` is used when evaluating an expression to resolve properties, methods, fields, and to help perform type conversion. The out-of-the-box -implementation, `StandardEvaluationContext`, uses reflection to manipulate the object, -caching `java.lang.reflect.Method`, `java.lang.reflect.Field`, and -`java.lang.reflect.Constructor` instances for increased performance. +implementations, `SimpleEvalutationContext` and `StandardEvaluationContext`, use +reflection to manipulate the object, caching `java.lang.reflect.Method`, +`java.lang.reflect.Field`, and `java.lang.reflect.Constructor` instances for increased +performance. -The `StandardEvaluationContext` is where you may specify the root object to evaluate -against via the method `setRootObject()` or passing the root object into the -constructor. You can also specify variables and functions that will be used in the -expression using the methods `setVariable()` and `registerFunction()`. The use of -variables and functions are described in the language reference sections -<> and <>. The -`StandardEvaluationContext` is also where you can register custom -``ConstructorResolver``s, ``MethodResolver``s, and ``PropertyAccessor``s to extend how SpEL -evaluates expressions. Please refer to the javadoc of these classes for more details. +`SimpleEvaluationContext` exposes a subset of essential SpEL language features and +configuration options. Certain categories of expressions, do not require the full extent +of the SpEL language syntax and arguably should be meaningfully restricted. Examples +include but are not limited to data binding expressions, property-based filters, and +others. To effect, `SimpleEvaluationContext` supports a subset of the SpEL language syntax +that excludes references to Java types, constructors, and bean references. + +`StandardEvaluationContext` exposes the full set of SpEL language features and +configuration options. You may use it to specify a default root object, and to configure +every available evaluation-related strategy. [[expressions-type-conversion]] -==== Type Conversion +==== Type conversion + By default SpEL uses the conversion service available in Spring core ( `org.springframework.core.convert.ConversionService`). This conversion service comes with many converters built in for common conversions but is also fully extensible so @@ -275,22 +223,25 @@ being placed in it. A simple example: } Simple simple = new Simple(); - simple.booleanList.add(true); - StandardEvaluationContext simpleContext = new StandardEvaluationContext(simple); + SimpleEvaluationContext context = new SimpleEvaluationContext(); // false is passed in here as a string. SpEL and the conversion service will // correctly recognize that it needs to be a Boolean and convert it - parser.parseExpression("booleanList[0]").setValue(simpleContext, "false"); + + parser.parseExpression("booleanList[0]").setValue(context, simple, "false"); // b will be false Boolean b = simple.booleanList.get(0); ---- + + [[expressions-parser-configuration]] === Parser configuration -It is possible to configure the SpEL expression parser using a parser configuration object + +It is possible to configure the SpEL expression parser using a parser configuration object (`org.springframework.expression.spel.SpelParserConfiguration`). The configuration object controls the behavior of some of the expression components. For example, if indexing into an array or collection and the element at the specified index is `null` @@ -325,6 +276,8 @@ list it is possible to automatically grow the array or list to accommodate that It is also possible to configure the behaviour of the SpEL expression compiler. + + [[expressions-spel-compilation]] === SpEL compilation @@ -356,6 +309,7 @@ gain can be very noticeable. In an example micro benchmark run of 50000 iteratio taking 75ms to evaluate using only the interpreter and just 3ms using the compiled version of the expression. + [[expressions-compiler-configuration]] ==== Compiler configuration @@ -415,6 +369,7 @@ In these cases it is possible to use a system property. The property `spring.expression.compiler.mode` can be set to one of the `SpelCompilerMode` enum values (`off`, `immediate`, or `mixed`). + [[expressions-compiler-limitations]] ==== Compiler limitations @@ -430,8 +385,12 @@ at the moment: More and more types of expression will be compilable in the future. + + + [[expressions-beandef]] -== Expression support for defining bean definitions +== Expressions in bean definitions + SpEL expressions can be used with XML or annotation-based configuration metadata for defining ``BeanDefinition``s. In both cases the syntax to define the expression is of the form `#{ }`. @@ -439,7 +398,8 @@ form `#{ }`. [[expressions-beandef-xml-based]] -=== XML based configuration +=== XML configuration + A property or constructor-arg value can be set using expressions as shown below. [source,xml,indent=0] @@ -487,7 +447,8 @@ You can also refer to other bean properties by name, for example. [[expressions-beandef-annotation-based]] -=== Annotation-based configuration +=== Annotation config + The `@Value` annotation can be placed on fields, methods and method/constructor parameters to specify a default value. @@ -584,6 +545,7 @@ Autowired methods and constructors can also use the `@Value` annotation. [[expressions-ref-literal]] === Literal expressions + The types of literal expressions supported are strings, numeric values (int, real, hex), boolean and null. Strings are delimited by single quotes. To put a single quote itself in a string, use two single quote characters. @@ -617,6 +579,7 @@ By default real numbers are parsed using Double.parseDouble(). [[expressions-properties-arrays]] === Properties, Arrays, Lists, Maps, Indexers + Navigating with property references is easy: just use a period to indicate a nested property value. The instances of the `Inventor` class, pupin, and tesla, were populated with data listed in the section <>. @@ -639,25 +602,24 @@ arrays and lists are obtained using square bracket notation. [subs="verbatim,quotes"] ---- ExpressionParser parser = new SpelExpressionParser(); + SimpleEvaluationContext context = new SimpleEvaluationContext(); // Inventions Array - StandardEvaluationContext teslaContext = new StandardEvaluationContext(tesla); // evaluates to "Induction motor" String invention = parser.parseExpression("inventions[3]").getValue( - teslaContext, String.class); + context, tesla, String.class); // Members List - StandardEvaluationContext societyContext = new StandardEvaluationContext(ieee); // evaluates to "Nikola Tesla" String name = parser.parseExpression("Members[0].Name").getValue( - societyContext, String.class); + context, ieee, String.class); // List and Array navigation // evaluates to "Wireless communication" String invention = parser.parseExpression("Members[0].Inventions[6]").getValue( - societyContext, String.class); + context, ieee, String.class); ---- The contents of maps are obtained by specifying the literal key value within the @@ -685,6 +647,7 @@ string literals. [[expressions-inline-lists]] === Inline lists + Lists can be expressed directly in an expression using `{}` notation. [source,java,indent=0] @@ -700,8 +663,11 @@ Lists can be expressed directly in an expression using `{}` notation. entirely composed of fixed literals then a constant list is created to represent the expression, rather than building a new list on each evaluation. + + [[expressions-inline-maps]] === Inline Maps + Maps can also be expressed directly in an expression using `{key:value}` notation. [source,java,indent=0] @@ -717,8 +683,11 @@ of fixed literals or other nested constant structures (lists or maps) then a con to represent the expression, rather than building a new map on each evaluation. Quoting of the map keys is optional, the examples above are not using quoted keys. + + [[expressions-array-construction]] === Array construction + Arrays can be built using the familiar Java syntax, optionally supplying an initializer to have the array populated at construction time. @@ -741,6 +710,7 @@ multi-dimensional array. [[expressions-methods]] === Methods + Methods are invoked using typical Java programming syntax. You may also invoke methods on literals. Varargs are also supported. @@ -763,6 +733,7 @@ on literals. Varargs are also supported. [[expressions-operators-relational]] ==== Relational operators + The relational operators; equal, not equal, less than, less than or equal, greater than, and greater than or equal are supported using standard operator notation. @@ -825,6 +796,7 @@ shown here: `lt` (`<`), `gt` (`>`), `le` (`<=`), `ge` (`>=`), `eq` (`==`), [[expressions-operators-logical]] ==== Logical operators + The logical operators that are supported are and, or, and not. Their use is demonstrated below. @@ -862,6 +834,7 @@ below. [[expressions-operators-mathematical]] ==== Mathematical operators + The addition operator can be used on both numbers and strings. Subtraction, multiplication and division can be used only on numbers. Other mathematical operators supported are modulus (%) and exponential power (^). Standard operator precedence is enforced. These @@ -904,6 +877,7 @@ operators are demonstrated below. [[expressions-assignment]] === Assignment + Setting of a property is done by using the assignment operator. This would typically be done within a call to `setValue` but can also be done inside a call to `getValue`. @@ -911,20 +885,21 @@ done within a call to `setValue` but can also be done inside a call to `getValue [subs="verbatim,quotes"] ---- Inventor inventor = new Inventor(); - StandardEvaluationContext inventorContext = new StandardEvaluationContext(inventor); + SimpleEvaluationContext context = new SimpleEvaluationContext(); - parser.parseExpression("Name").setValue(inventorContext, "Alexander Seovic2"); + parser.parseExpression("Name").setValue(context, inventor, "Alexander Seovic2"); // alternatively String aleks = parser.parseExpression( - "Name = 'Alexandar Seovic'").getValue(inventorContext, String.class); + "Name = 'Alexandar Seovic'").getValue(context, inventor, String.class); ---- [[expressions-types]] === Types + The special `T` operator can be used to specify an instance of java.lang.Class (the _type_). Static methods are invoked using this operator as well. The `StandardEvaluationContext` uses a `TypeLocator` to find types and the @@ -948,6 +923,7 @@ fully qualified, but all other type references must be. [[expressions-constructors]] === Constructors + Constructors can be invoked using the new operator. The fully qualified class name should be used for all but the primitive type and String (where int, float, etc, can be used). @@ -969,17 +945,18 @@ used). [[expressions-ref-variables]] === Variables + Variables can be referenced in the expression using the syntax `#variableName`. Variables -are set using the method setVariable on the `StandardEvaluationContext`. +are set using the method setVariable on `EvaluationContext` implementations. [source,java,indent=0] [subs="verbatim,quotes"] ---- Inventor tesla = new Inventor("Nikola Tesla", "Serbian"); - StandardEvaluationContext context = new StandardEvaluationContext(tesla); + SimpleEvaluationContext context = new SimpleEvaluationContext(); context.setVariable("newName", "Mike Tesla"); - parser.parseExpression("Name = #newName").getValue(context); + parser.parseExpression("Name = #newName").getValue(context, tesla); System.out.println(tesla.getName()) // "Mike Tesla" ---- @@ -987,6 +964,7 @@ are set using the method setVariable on the `StandardEvaluationContext`. [[expressions-this-root]] ==== The #this and #root variables + The variable #this is always defined and refers to the current evaluation object (against which unqualified references are resolved). The variable #root is always defined and refers to the root context object. Although #this may vary as components of @@ -1001,7 +979,7 @@ an expression are evaluated, #root always refers to the root. // create parser and set variable 'primes' as the array of integers ExpressionParser parser = new SpelExpressionParser(); - StandardEvaluationContext context = new StandardEvaluationContext(); + SimpleEvaluationContext context = new SimpleEvaluationContext(); context.setVariable("primes",primes); // all prime numbers > 10 from the list (using selection ?{...}) @@ -1014,18 +992,20 @@ an expression are evaluated, #root always refers to the root. [[expressions-ref-functions]] === Functions + You can extend SpEL by registering user defined functions that can be called within the -expression string. The function is registered with the `StandardEvaluationContext` using -the method. +expression string. The function is registered through the `EvaluationContext`. [source,java,indent=0] [subs="verbatim,quotes"] ---- - public void registerFunction(String name, Method m) + Method method = ...; + + SimpleEvaluationContext context = new SimpleEvaluationContext(); + context.setVariable("myFunction", method); ---- -A reference to a Java Method provides the implementation of the function. For example, a -utility method to reverse a string is shown below. +For example, given a utility method to reverse a string is shown below: [source,java,indent=0] [subs="verbatim,quotes"] @@ -1042,16 +1022,15 @@ utility method to reverse a string is shown below. } ---- -This method is then registered with the evaluation context and can be used within an -expression string. +The above method can then be registered and used as follows: [source,java,indent=0] [subs="verbatim,quotes"] ---- ExpressionParser parser = new SpelExpressionParser(); - StandardEvaluationContext context = new StandardEvaluationContext(); + SimpleEvaluationContext context = new SimpleEvaluationContext(); - context.registerFunction("reverseString", + context.setVariable("reverseString", StringUtils.class.getDeclaredMethod("reverseString", String.class)); String helloWorldReversed = parser.parseExpression( @@ -1062,6 +1041,7 @@ expression string. [[expressions-bean-references]] === Bean references + If the evaluation context has been configured with a bean resolver it is possible to lookup beans from an expression using the (@) symbol. @@ -1092,6 +1072,7 @@ To access a factory bean itself, the bean name should instead be prefixed with a [[expressions-operator-ternary]] === Ternary Operator (If-Then-Else) + You can use the ternary operator for performing if-then-else conditional logic inside the expression. A minimal example is: @@ -1126,6 +1107,7 @@ ternary operator. [[expressions-operator-elvis]] === The Elvis Operator + The Elvis operator is a shortening of the ternary operator syntax and is used in the http://www.groovy-lang.org/operators.html#_elvis_operator[Groovy] language. With the ternary operator syntax you usually have to repeat a variable twice, for @@ -1158,15 +1140,15 @@ Here is a more complex example. ExpressionParser parser = new SpelExpressionParser(); Inventor tesla = new Inventor("Nikola Tesla", "Serbian"); - StandardEvaluationContext context = new StandardEvaluationContext(tesla); + SimpleEvaluationContext context = new SimpleEvaluationContext(); - String name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, String.class); + String name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, tesla, String.class); System.out.println(name); // Nikola Tesla tesla.setName(null); - name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, String.class); + name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, tesla, String.class); System.out.println(name); // Elvis Presley ---- @@ -1175,6 +1157,7 @@ Here is a more complex example. [[expressions-operator-safe-navigation]] === Safe Navigation operator + The Safe Navigation operator is used to avoid a `NullPointerException` and comes from the http://www.groovy-lang.org/operators.html#_safe_navigation_operator[Groovy] language. Typically when you have a reference to an object you might need to verify that @@ -1189,14 +1172,14 @@ safe navigation operator will simply return null instead of throwing an exceptio Inventor tesla = new Inventor("Nikola Tesla", "Serbian"); tesla.setPlaceOfBirth(new PlaceOfBirth("Smiljan")); - StandardEvaluationContext context = new StandardEvaluationContext(tesla); + SimpleEvaluationContext context = new SimpleEvaluationContext(); - String city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, String.class); + String city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, tesla, String.class); System.out.println(city); // Smiljan tesla.setPlaceOfBirth(null); - city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, String.class); + city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, tesla, String.class); System.out.println(city); // null - does not throw NullPointerException!!! ---- @@ -1219,6 +1202,7 @@ This will inject a system property `pop3.port` if it is defined or 25 if not. [[expressions-collection-selection]] === Collection Selection + Selection is a powerful expression language feature that allows you to transform some source collection into another by selecting from its entries.