diff --git a/build.gradle b/build.gradle index 293b8b9b..cf2c7fe0 100644 --- a/build.gradle +++ b/build.gradle @@ -67,8 +67,8 @@ configure(subprojects.findAll { } subproject.ext { - springVersion = "5.0.3.RELEASE" - springSecurityVersion = "5.0.1.RELEASE" + springVersion = "5.0.5.BUILD-SNAPSHOT" + springSecurityVersion = "5.0.3.RELEASE" servletVersion = "3.1.0" hibernate5Version = "5.2.12.Final" tiles3Version = "3.0.7" diff --git a/spring-binding/src/main/java/org/springframework/binding/expression/spel/EvaluationContextFactory.java b/spring-binding/src/main/java/org/springframework/binding/expression/spel/EvaluationContextFactory.java new file mode 100644 index 00000000..5604bbdc --- /dev/null +++ b/spring-binding/src/main/java/org/springframework/binding/expression/spel/EvaluationContextFactory.java @@ -0,0 +1,35 @@ +/* + * 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.binding.expression.spel; + +import org.springframework.expression.EvaluationContext; + +/** + * Factory to create the {@link EvaluationContext} for a given root Object. + * + * @author Rossen Stoyanchev + * @since 2.4.8 + */ +public interface EvaluationContextFactory { + + /** + * Create an {@link EvaluationContext} for the given root object. + * @param rootObject the root object + * @return the created context instance + */ + EvaluationContext createContext(Object rootObject); + +} diff --git a/spring-binding/src/main/java/org/springframework/binding/expression/spel/SimpleEvaluationContextFactory.java b/spring-binding/src/main/java/org/springframework/binding/expression/spel/SimpleEvaluationContextFactory.java new file mode 100644 index 00000000..ece1719a --- /dev/null +++ b/spring-binding/src/main/java/org/springframework/binding/expression/spel/SimpleEvaluationContextFactory.java @@ -0,0 +1,67 @@ +/* + * 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.binding.expression.spel; + +import java.util.List; + +import org.springframework.binding.convert.ConversionService; +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.PropertyAccessor; +import org.springframework.expression.spel.support.DataBindingPropertyAccessor; +import org.springframework.expression.spel.support.SimpleEvaluationContext; + +/** + * Creates {@link SimpleEvaluationContext}, for use with data binding. + * + * @author Rossen Stoyanchev + * @since 2.4.8 + */ +public class SimpleEvaluationContextFactory implements EvaluationContextFactory { + + private static final PropertyAccessor dataBindingPropertyAccessor = + DataBindingPropertyAccessor.forReadWriteAccess(); + + + private final List propertyAccessors; + + private final ConversionService conversionService; + + + public SimpleEvaluationContextFactory(List propertyAccessors, + ConversionService conversionService) { + + this.propertyAccessors = propertyAccessors; + this.conversionService = conversionService; + } + + + @Override + public EvaluationContext createContext(Object rootObject) { + return SimpleEvaluationContext + .forPropertyAccessors(getAccessorsArray()) + .withConversionService(conversionService.getDelegateConversionService()) + .withRootObject(rootObject) + .build(); + } + + private PropertyAccessor[] getAccessorsArray() { + int length = propertyAccessors.size() + 1; + PropertyAccessor[] result = propertyAccessors.toArray(new PropertyAccessor[length]); + result[length - 1] = dataBindingPropertyAccessor; + return result; + } + +} diff --git a/spring-binding/src/main/java/org/springframework/binding/expression/spel/SpringELExpression.java b/spring-binding/src/main/java/org/springframework/binding/expression/spel/SpringELExpression.java index d8310522..6e7901fe 100644 --- a/spring-binding/src/main/java/org/springframework/binding/expression/spel/SpringELExpression.java +++ b/spring-binding/src/main/java/org/springframework/binding/expression/spel/SpringELExpression.java @@ -1,5 +1,5 @@ /* - * Copyright 2004-2014 the original author or authors. + * Copyright 2004-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. @@ -15,8 +15,6 @@ */ package org.springframework.binding.expression.spel; -import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Map; @@ -25,11 +23,11 @@ import org.springframework.binding.expression.Expression; import org.springframework.binding.expression.PropertyNotFoundException; import org.springframework.binding.expression.ValueCoercionException; import org.springframework.core.convert.ConversionService; +import org.springframework.expression.EvaluationContext; import org.springframework.expression.PropertyAccessor; import org.springframework.expression.spel.SpelEvaluationException; import org.springframework.expression.spel.SpelMessage; import org.springframework.expression.spel.support.StandardEvaluationContext; -import org.springframework.expression.spel.support.StandardTypeConverter; import org.springframework.util.Assert; /** @@ -45,17 +43,14 @@ public class SpringELExpression implements Expression { private final Class expectedType; - private final Map expressionVariables; + private final EvaluationContextFactory contextFactory; - private final ConversionService conversionService; - - private final List propertyAccessors; /** * Constructor for SpringELExpression. * * @param expression a parsed Spring EL expression instance. Must not be null. - * @param expressionVariables provides a mapping between variables names and + * @param expressionVars provides a mapping between variables names and * parsed Spring EL expression instances. * This parameter is optional (may be null). * @param expectedType the target type expected from the evaluation of the expression or null. @@ -64,24 +59,39 @@ public class SpringELExpression implements Expression { * @param propertyAccessors propertyAccessors for Spring EL to use when evaluating expressions */ public SpringELExpression(org.springframework.expression.Expression expression, - Map expressionVariables, Class expectedType, ConversionService conversionService, + Map expressionVars, Class expectedType, ConversionService conversionService, List propertyAccessors) { + this(expression, expectedType, + new StandardEvaluationContextFactory(propertyAccessors, conversionService, expressionVars)); + } + + /** + * Generalized constructor variant that accepts an {@link EvaluationContextFactory}. + * @since 2.4.8 + */ + public SpringELExpression(org.springframework.expression.Expression expression, Class expectedType, + EvaluationContextFactory contextFactory) { + Assert.notNull(expression, "The SpelExpression is required for evaluation"); + Assert.notNull(contextFactory, "The EvaluationContextFactory is required"); this.expression = expression; - this.expressionVariables = expressionVariables; this.expectedType = expectedType; - this.conversionService = conversionService; - this.propertyAccessors = propertyAccessors; + this.contextFactory = contextFactory; } public String getExpressionString() { return expression.getExpressionString(); } + @SuppressWarnings("deprecation") public Object getValue(Object rootObject) throws EvaluationException { try { - return expression.getValue(createEvaluationContext(rootObject), expectedType); + EvaluationContext context = contextFactory.createContext(rootObject); + if (context instanceof StandardEvaluationContext) { + extendEvaluationContext((StandardEvaluationContext) context); + } + return expression.getValue(context, expectedType); } catch (SpelEvaluationException e) { if (e.getMessageCode().equals(SpelMessage.PROPERTY_OR_FIELD_NOT_READABLE)) { throw new PropertyNotFoundException(rootObject.getClass(), getExpressionString(), e); @@ -95,9 +105,14 @@ public class SpringELExpression implements Expression { } } + @SuppressWarnings("deprecation") public Class getValueType(Object rootObject) throws EvaluationException { try { - return expression.getValueType(createEvaluationContext(rootObject)); + EvaluationContext context = contextFactory.createContext(rootObject); + if (context instanceof StandardEvaluationContext) { + extendEvaluationContext((StandardEvaluationContext) context); + } + return expression.getValueType(context); } catch (SpelEvaluationException e) { if (e.getMessageCode().equals(SpelMessage.PROPERTY_OR_FIELD_NOT_READABLE)) { throw new PropertyNotFoundException(rootObject.getClass(), getExpressionString(), e); @@ -108,10 +123,14 @@ public class SpringELExpression implements Expression { } } + @SuppressWarnings("deprecation") public void setValue(Object rootObject, Object value) throws EvaluationException { try { - StandardEvaluationContext evaluationContext = createEvaluationContext(rootObject); - expression.setValue(evaluationContext, value); + EvaluationContext context = contextFactory.createContext(rootObject); + if (context instanceof StandardEvaluationContext) { + extendEvaluationContext((StandardEvaluationContext) context); + } + expression.setValue(context, value); } catch (SpelEvaluationException e) { if (e.getMessageCode().equals(SpelMessage.PROPERTY_OR_FIELD_NOT_WRITABLE)) { throw new PropertyNotFoundException(rootObject.getClass(), getExpressionString(), e); @@ -125,43 +144,16 @@ public class SpringELExpression implements Expression { } } - /** - * Create a new Spring EL evaluation context for the given rootObject. - */ - private StandardEvaluationContext createEvaluationContext(Object rootObject) { - StandardEvaluationContext context = new StandardEvaluationContext(rootObject); - context.setVariables(getVariableValues(rootObject)); - context.setTypeConverter(new StandardTypeConverter(conversionService)); - context.getPropertyAccessors().addAll(propertyAccessors); - extendEvaluationContext(context); - return context; - } - /** * Invoked every time an evaluation context is created allowing further * initialization from sub-classes. + * @deprecated as of 2.4.8, to customize the context, please use the constructor + * that accepts an {@link EvaluationContextFactory}. */ + @Deprecated protected void extendEvaluationContext(StandardEvaluationContext context) { } - /** - * Turn the map of variable-names-to-expressions into a map of variable-names-to-plain-objects - * by evaluating each object against the input rootObject. - * - * @param rootObject the Object to evaluate variable expressions against. - * @return a mapping between variables names and plain Object's. - */ - private Map getVariableValues(Object rootObject) { - if (expressionVariables == null) { - return Collections.emptyMap(); - } - Map variableValues = new HashMap<>(expressionVariables.size()); - for (Map.Entry var : expressionVariables.entrySet()) { - variableValues.put(var.getKey(), var.getValue().getValue(rootObject)); - } - return variableValues; - } - public String toString() { return getExpressionString(); } diff --git a/spring-binding/src/main/java/org/springframework/binding/expression/spel/SpringELExpressionParser.java b/spring-binding/src/main/java/org/springframework/binding/expression/spel/SpringELExpressionParser.java index caca9e55..eb2268f9 100644 --- a/spring-binding/src/main/java/org/springframework/binding/expression/spel/SpringELExpressionParser.java +++ b/spring-binding/src/main/java/org/springframework/binding/expression/spel/SpringELExpressionParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2004-2017 the original author or authors. + * Copyright 2004-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. @@ -28,6 +28,7 @@ import org.springframework.binding.expression.ExpressionVariable; import org.springframework.binding.expression.ParserContext; import org.springframework.binding.expression.ParserException; import org.springframework.binding.expression.support.NullParserContext; +import org.springframework.binding.expression.support.SimpleParserContext; import org.springframework.context.expression.MapAccessor; import org.springframework.expression.EvaluationContext; import org.springframework.expression.PropertyAccessor; @@ -49,6 +50,9 @@ public class SpringELExpressionParser implements ExpressionParser { private final List propertyAccessors = new ArrayList<>(); + private final SimpleEvaluationContextFactory simpleContextFactory; + + public SpringELExpressionParser(SpelExpressionParser expressionParser) { this(expressionParser, new DefaultConversionService()); } @@ -57,6 +61,7 @@ public class SpringELExpressionParser implements ExpressionParser { this.expressionParser = expressionParser; this.propertyAccessors.add(new MapAccessor()); this.conversionService = conversionService; + this.simpleContextFactory = new SimpleEvaluationContextFactory(this.propertyAccessors, conversionService); } public ConversionService getConversionService() { @@ -78,11 +83,17 @@ public class SpringELExpressionParser implements ExpressionParser { Class expectedResultType = context.getExpectedEvaluationResultType(); org.springframework.core.convert.ConversionService cs = conversionService.getDelegateConversionService(); - return createSpringELExpression(expressionVars, spelExpression, expectedResultType, cs); + return context instanceof SimpleParserContext ? + new SpringELExpression(spelExpression, expectedResultType, simpleContextFactory) : + createSpringELExpression(expressionVars, spelExpression, expectedResultType, cs); } /** * Create the {@link SpringELExpression}. + *

Note: as of 2.4.8, this method is not invoked when a + * {@link SimpleParserContext} is passed in, which is mainly the case when using + * SpEL for data binding. In those scenarios, the configuration options are + * limited to the use of property accessors and a ConversionService. */ protected SpringELExpression createSpringELExpression(Map expressionVars, org.springframework.expression.Expression spelExpression, Class expectedResultType, diff --git a/spring-binding/src/main/java/org/springframework/binding/expression/spel/StandardEvaluationContextFactory.java b/spring-binding/src/main/java/org/springframework/binding/expression/spel/StandardEvaluationContextFactory.java new file mode 100644 index 00000000..d51a5a27 --- /dev/null +++ b/spring-binding/src/main/java/org/springframework/binding/expression/spel/StandardEvaluationContextFactory.java @@ -0,0 +1,79 @@ +/* + * 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.binding.expression.spel; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.springframework.binding.expression.Expression; +import org.springframework.core.convert.ConversionService; +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.PropertyAccessor; +import org.springframework.expression.spel.support.StandardEvaluationContext; +import org.springframework.expression.spel.support.StandardTypeConverter; + +/** + * Creates a {@link StandardEvaluationContext} enabling the full power of SpEL. + * + * @author Rossen Stoyanchev + * @since 2.4.8 + */ +public class StandardEvaluationContextFactory implements EvaluationContextFactory { + + private final List propertyAccessors; + + private final ConversionService conversionService; + + private final Map expressionVariables; + + + public StandardEvaluationContextFactory(List propertyAccessors, + ConversionService conversionService, Map expressionVariables) { + + this.propertyAccessors = propertyAccessors; + this.conversionService = conversionService; + this.expressionVariables = expressionVariables; + } + + + @Override + public EvaluationContext createContext(Object rootObject) { + StandardEvaluationContext context = new StandardEvaluationContext(rootObject); + context.setVariables(getVariableValues(rootObject)); + context.setTypeConverter(new StandardTypeConverter(conversionService)); + context.getPropertyAccessors().addAll(propertyAccessors); + return context; + } + + /** + * Turn the map of variable-names-to-expressions into a map of variable-names-to-plain-objects + * by evaluating each object against the input rootObject. + * @param rootObject the Object to evaluate variable expressions against. + * @return a mapping between variables names and plain Object's. + */ + private Map getVariableValues(Object rootObject) { + if (expressionVariables == null) { + return Collections.emptyMap(); + } + Map variableValues = new HashMap<>(expressionVariables.size()); + for (Map.Entry var : expressionVariables.entrySet()) { + variableValues.put(var.getKey(), var.getValue().getValue(rootObject)); + } + return variableValues; + } +} diff --git a/spring-binding/src/main/java/org/springframework/binding/expression/support/SimpleParserContext.java b/spring-binding/src/main/java/org/springframework/binding/expression/support/SimpleParserContext.java new file mode 100644 index 00000000..25500664 --- /dev/null +++ b/spring-binding/src/main/java/org/springframework/binding/expression/support/SimpleParserContext.java @@ -0,0 +1,57 @@ +/* + * Copyright 2004-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.binding.expression.support; + +import org.springframework.binding.expression.ExpressionVariable; +import org.springframework.binding.expression.ParserContext; + +/** + * {@link ParserContext} for use in data binding scenarios, providing + * {@link org.springframework.binding.expression.ExpressionParser ExpressionParser} + * implementations with a hint that they can meaningfully restrict the EL feature set. + * Specifically designed for use with the Spring EL + * {@link org.springframework.expression.spel.support.SimpleEvaluationContext + * SimpleEvaluationContext}. + * + * @author Rossen Stoyanchev + * @since 2.4.8 + */ +public final class SimpleParserContext implements ParserContext { + + private Class evaluationContextType; + + + public SimpleParserContext(Class evaluationContextType) { + this.evaluationContextType = evaluationContextType; + } + + + public Class getEvaluationContextType() { + return this.evaluationContextType; + } + + public Class getExpectedEvaluationResultType() { + return null; + } + + public ExpressionVariable[] getExpressionVariables() { + return null; + } + + public boolean isTemplate() { + return false; + } +} diff --git a/spring-webflow/src/main/java/org/springframework/webflow/mvc/view/AbstractMvcView.java b/spring-webflow/src/main/java/org/springframework/webflow/mvc/view/AbstractMvcView.java index c98814a4..4fdb0982 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/mvc/view/AbstractMvcView.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/mvc/view/AbstractMvcView.java @@ -1,5 +1,5 @@ /* - * Copyright 2004-2014 the original author or authors. + * Copyright 2004-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. @@ -23,7 +23,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; - import javax.lang.model.SourceVersion; import org.apache.commons.logging.Log; @@ -38,7 +37,7 @@ import org.springframework.binding.expression.Expression; import org.springframework.binding.expression.ExpressionParser; import org.springframework.binding.expression.ParserContext; import org.springframework.binding.expression.beanwrapper.BeanWrapperExpressionParser; -import org.springframework.binding.expression.support.FluentParserContext; +import org.springframework.binding.expression.support.SimpleParserContext; import org.springframework.binding.expression.support.StaticExpression; import org.springframework.binding.mapping.MappingResult; import org.springframework.binding.mapping.MappingResults; @@ -443,7 +442,7 @@ public abstract class AbstractMvcView implements View { */ protected void addMapping(DefaultMapper mapper, Binding binding, Object model) { Expression source = new RequestParameterExpression(binding.getProperty()); - ParserContext parserContext = new FluentParserContext().evaluate(model.getClass()); + ParserContext parserContext = new SimpleParserContext(model.getClass()); Expression target = expressionParser.parseExpression(binding.getProperty(), parserContext); DefaultMapping mapping = new DefaultMapping(source, target); mapping.setRequired(binding.getRequired()); @@ -490,7 +489,7 @@ public abstract class AbstractMvcView implements View { * @param model the model */ protected void addEmptyValueMapping(DefaultMapper mapper, String field, Object model) { - ParserContext parserContext = new FluentParserContext().evaluate(model.getClass()); + ParserContext parserContext = new SimpleParserContext(model.getClass()); Expression target = emptyValueExpressionParser.parseExpression(field, parserContext); try { Class propertyType = target.getValueType(model); @@ -513,7 +512,7 @@ public abstract class AbstractMvcView implements View { */ protected void addDefaultMapping(DefaultMapper mapper, String parameter, Object model) { Expression source = new RequestParameterExpression(parameter); - ParserContext parserContext = new FluentParserContext().evaluate(model.getClass()); + ParserContext parserContext = new SimpleParserContext(model.getClass()); if (expressionParser instanceof BeanWrapperExpressionParser || checkModelProperty(parameter, model)) { Expression target = expressionParser.parseExpression(parameter, parserContext); DefaultMapping mapping = new DefaultMapping(source, target);