Use SimpleEvaluationContext in AbstractMvcView

Issue: SWF-1722
This commit is contained in:
Rossen Stoyanchev
2018-03-27 13:41:57 -04:00
parent 13d36e59a8
commit 962cb673cf
8 changed files with 297 additions and 57 deletions

View File

@@ -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"

View File

@@ -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);
}

View File

@@ -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<PropertyAccessor> propertyAccessors;
private final ConversionService conversionService;
public SimpleEvaluationContextFactory(List<PropertyAccessor> 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;
}
}

View File

@@ -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<String, Expression> expressionVariables;
private final EvaluationContextFactory contextFactory;
private final ConversionService conversionService;
private final List<PropertyAccessor> 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<String, Expression> expressionVariables, Class<?> expectedType, ConversionService conversionService,
Map<String, Expression> expressionVars, Class<?> expectedType, ConversionService conversionService,
List<PropertyAccessor> 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<String, Object> getVariableValues(Object rootObject) {
if (expressionVariables == null) {
return Collections.emptyMap();
}
Map<String, Object> variableValues = new HashMap<>(expressionVariables.size());
for (Map.Entry<String, Expression> var : expressionVariables.entrySet()) {
variableValues.put(var.getKey(), var.getValue().getValue(rootObject));
}
return variableValues;
}
public String toString() {
return getExpressionString();
}

View File

@@ -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<PropertyAccessor> 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}.
* <p><strong>Note:</strong> 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<String, Expression> expressionVars,
org.springframework.expression.Expression spelExpression, Class<?> expectedResultType,

View File

@@ -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<PropertyAccessor> propertyAccessors;
private final ConversionService conversionService;
private final Map<String, Expression> expressionVariables;
public StandardEvaluationContextFactory(List<PropertyAccessor> propertyAccessors,
ConversionService conversionService, Map<String, Expression> 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<String, Object> getVariableValues(Object rootObject) {
if (expressionVariables == null) {
return Collections.emptyMap();
}
Map<String, Object> variableValues = new HashMap<>(expressionVariables.size());
for (Map.Entry<String, Expression> var : expressionVariables.entrySet()) {
variableValues.put(var.getKey(), var.getValue().getValue(rootObject));
}
return variableValues;
}
}

View File

@@ -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;
}
}

View File

@@ -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);