SWF-1136 First cut of Spring Binding adapters for Spring EL. Results verified with same test cases as what is there for the Unified EL adapters.

This commit is contained in:
Rossen Stoyanchev
2010-04-23 10:55:30 +00:00
parent 89783b5b9e
commit 4aff050e81
8 changed files with 542 additions and 56 deletions

View File

@@ -15,6 +15,7 @@
<classpathentry kind="var" path="IVY_CACHE/org.springframework/org.springframework.beans/3.0.2.RELEASE/org.springframework.beans-3.0.2.RELEASE.jar" sourcepath="/IVY_CACHE/org.springframework/org.springframework.beans/3.0.2.RELEASE/org.springframework.beans-sources-3.0.2.RELEASE.jar"/>
<classpathentry kind="var" path="IVY_CACHE/org.springframework/org.springframework.context/3.0.2.RELEASE/org.springframework.context-3.0.2.RELEASE.jar" sourcepath="/IVY_CACHE/org.springframework/org.springframework.context/3.0.2.RELEASE/org.springframework.context-sources-3.0.2.RELEASE.jar"/>
<classpathentry kind="var" path="IVY_CACHE/org.springframework/org.springframework.core/3.0.2.RELEASE/org.springframework.core-3.0.2.RELEASE.jar" sourcepath="/IVY_CACHE/org.springframework/org.springframework.core/3.0.2.RELEASE/org.springframework.core-sources-3.0.2.RELEASE.jar"/>
<classpathentry kind="var" path="IVY_CACHE/org.springframework/org.springframework.expression/3.0.2.RELEASE/org.springframework.expression-3.0.2.RELEASE.jar" sourcepath="/IVY_CACHE/org.springframework/org.springframework.expression/3.0.2.RELEASE/org.springframework.expression-sources-3.0.2.RELEASE.jar"/>
<classpathentry kind="var" path="IVY_CACHE/org.ognl/com.springsource.org.ognl/2.6.9/com.springsource.org.ognl-2.6.9.jar" sourcepath="/IVY_CACHE/org.ognl/com.springsource.org.ognl/2.6.9/com.springsource.org.ognl-sources-2.6.9.jar"/>
<classpathentry kind="output" path="target/classes"/>
</classpath>

View File

@@ -26,6 +26,7 @@
<dependency org="org.springframework" name="org.springframework.beans" rev="3.0.2.RELEASE" conf="compile->runtime"/>
<dependency org="org.springframework" name="org.springframework.context" rev="3.0.2.RELEASE" conf="compile->runtime"/>
<dependency org="org.springframework" name="org.springframework.core" rev="3.0.2.RELEASE" conf="compile->runtime"/>
<dependency org="org.springframework" name="org.springframework.expression" rev="3.0.2.RELEASE" conf="compile->runtime"/>
<!-- test-time only dependencies -->
<dependency org="org.apache.log4j" name="com.springsource.org.apache.log4j" rev="1.2.15" conf="test->runtime"/>

View File

@@ -27,6 +27,11 @@
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>ognl</groupId>
<artifactId>ognl</artifactId>

View File

@@ -0,0 +1,136 @@
/*
* Copyright 2004-2010 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.Iterator;
import java.util.Map;
import org.springframework.binding.expression.EvaluationException;
import org.springframework.binding.expression.Expression;
import org.springframework.binding.expression.ValueCoercionException;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.util.Assert;
/**
* <p>
* A wrapper for a Spring EL {@link org.springframework.expression.Expression} allowing it to be used under the Spring
* Binding {@link Expression} abstraction.
* </p>
*
* @author Rossen Stoyanchev
* @since 2.1.0
*/
public class SpringELExpression implements Expression {
private org.springframework.expression.Expression expression;
private StandardEvaluationContext evaluationContext;
private Class expectedType;
private Map expressionVariables;
/**
* Constructor for SpringELExpression.
*
* @param expression a parsed Spring EL expression instance. Must not be null.
* @param expressionVariables 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. This parameter is
* optional (may be null).
* @param evaluationContext a Spring EL evaluation context.
*/
public SpringELExpression(org.springframework.expression.Expression expression, Map expressionVariables,
Class expectedType, StandardEvaluationContext evaluationContext) {
Assert.notNull(expression, "The SpelExpression is required for evaluation");
Assert.notNull(evaluationContext, "The StandardEvaluationContext is required for evaluation");
this.expression = expression;
this.expressionVariables = expressionVariables;
this.expectedType = expectedType;
this.evaluationContext = evaluationContext;
}
public String getExpressionString() {
return expression.getExpressionString();
}
public Object getValue(Object rootObject) throws EvaluationException {
try {
updateEvaluationContext(rootObject);
return expression.getValue(evaluationContext, expectedType);
} catch (SpelEvaluationException e) {
if (e.getMessageCode().equals(SpelMessage.TYPE_CONVERSION_ERROR)) {
throw new ValueCoercionException(rootObject.getClass(), getExpressionString(), null, expectedType, e);
}
throw new EvaluationException(rootObject.getClass(), expression.getExpressionString(),
"An ELException occurred getting the value for expression '" + getExpressionString()
+ "' on context [" + rootObject.getClass() + "]", e);
}
}
public Class getValueType(Object rootObject) throws EvaluationException {
evaluationContext.setRootObject(rootObject);
return expression.getValueType(evaluationContext);
}
public void setValue(Object rootObject, Object value) throws EvaluationException {
try {
updateEvaluationContext(rootObject);
expression.setValue(evaluationContext, value);
} catch (SpelEvaluationException e) {
if (e.getMessageCode().equals(SpelMessage.EXCEPTION_DURING_PROPERTY_WRITE)) {
throw new ValueCoercionException(rootObject.getClass(), getExpressionString(), value, expectedType, e);
}
throw new EvaluationException(rootObject.getClass(), getExpressionString(),
"An ELException occurred setting the value of expression '" + getExpressionString()
+ "' on context [" + rootObject.getClass() + "] to [" + value + "]", e);
}
}
/**
* Updates the Spring EL evaluation context to reflect the given rootObject.
*
* @param rootObject the object for the evaluation.
*/
private void updateEvaluationContext(Object rootObject) {
evaluationContext.setRootObject(rootObject);
evaluationContext.setVariables(getVariableValues(rootObject));
}
/**
* Turns 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.EMPTY_MAP;
}
Map variableValues = new HashMap(expressionVariables.size());
for (Iterator iterator = expressionVariables.entrySet().iterator(); iterator.hasNext();) {
Map.Entry var = (Map.Entry) iterator.next();
variableValues.put(var.getKey(), ((Expression) var.getValue()).getValue(rootObject));
}
return variableValues;
}
}

View File

@@ -0,0 +1,106 @@
/*
* Copyright 2004-2010 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.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.binding.expression.Expression;
import org.springframework.binding.expression.ExpressionParser;
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.core.convert.ConversionService;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.PropertyAccessor;
import org.springframework.expression.TypeConverter;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.expression.spel.support.StandardTypeConverter;
import org.springframework.util.Assert;
/**
* <p>
* Adapts the Spring EL {@link SpelExpressionParser} to the Spring Binding {@link ExpressionParser} interface.
* </p>
*
* @author Rossen Stoyanchev
* @since 2.1.0
*/
public class SpringELExpressionParser implements ExpressionParser {
private SpelExpressionParser expressionParser;
private TypeConverter typeConverter = new StandardTypeConverter();
private List propertyAccessors = new ArrayList();
public SpringELExpressionParser(SpelExpressionParser expressionParser) {
this.expressionParser = expressionParser;
}
public void setConversionService(ConversionService conversionService) {
typeConverter = new StandardTypeConverter(conversionService);
}
public void addPropertyAccessor(PropertyAccessor propertyAccessor) {
propertyAccessors.add(propertyAccessor);
}
public Expression parseExpression(String expressionString, ParserContext parserContext) throws ParserException {
Assert.hasText(expressionString, "The expression string to parse is required and must not be empty");
parserContext = (parserContext == null) ? NullParserContext.INSTANCE : parserContext;
StandardEvaluationContext evaluationContext = new StandardEvaluationContext();
evaluationContext.setTypeConverter(typeConverter);
evaluationContext.getPropertyAccessors().addAll(propertyAccessors);
Map spelExpressionVariables = parseSpelExpressionVariables(parserContext.getExpressionVariables());
return new SpringELExpression(parseSpelExpression(expressionString, parserContext), spelExpressionVariables,
parserContext.getExpectedEvaluationResultType(), evaluationContext);
}
private org.springframework.expression.Expression parseSpelExpression(String expression, ParserContext parserContext) {
return expressionParser.parseExpression(expression, getSpelParserContext(parserContext));
}
private org.springframework.expression.ParserContext getSpelParserContext(ParserContext parserContext) {
return parserContext.isTemplate() ? org.springframework.expression.ParserContext.TEMPLATE_EXPRESSION : null;
}
/**
* Turns {@link ExpressionVariable}'s (pairs of variable names and string expressions) into a map of variable names
* and parsed Spring EL expressions. The map will be saved in a Spring EL {@link EvaluationContext} for later use at
* evaluation time.
*
* @param expressionVariables an array of ExpressionVariable instances.
* @return a Map or null if the input array is empty.
*/
private Map parseSpelExpressionVariables(ExpressionVariable[] expressionVariables) {
if (expressionVariables == null || expressionVariables.length == 0) {
return null;
}
Map spelExpressionVariables = new HashMap(expressionVariables.length);
for (int i = 0; i < expressionVariables.length; i++) {
ExpressionVariable var = expressionVariables[i];
spelExpressionVariables.put(var.getName(),
parseExpression(var.getValueExpression(), var.getParserContext()));
}
return spelExpressionVariables;
}
}

View File

@@ -1,8 +1,6 @@
package org.springframework.binding.expression.el;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.el.ELContext;
import javax.el.ELResolver;
@@ -198,60 +196,6 @@ public class ELExpressionParserTests extends TestCase {
}
}
public static class TestBean {
private String value = "foo";
private int maximum = 2;
private TestBean bean;
private List list = new ArrayList();
public TestBean() {
initList();
}
public TestBean(TestBean bean) {
this.bean = bean;
initList();
}
private void initList() {
list.add("1");
list.add("2");
list.add("3");
list.add(null);
}
public TestBean getBean() {
return bean;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public String encode(String data) {
return "!" + data;
}
public int getMaximum() {
return maximum;
}
public void setMaximum(int maximum) {
this.maximum = maximum;
}
public List getList() {
return list;
}
}
private static class TestELContextFactory implements ELContextFactory {
public ELContext getELContext(final Object target) {
return new ELContext() {

View File

@@ -0,0 +1,71 @@
/*
* Copyright 2004-2007 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.el;
import java.util.ArrayList;
import java.util.List;
public class TestBean {
private String value = "foo";
private int maximum = 2;
private TestBean bean;
private List list = new ArrayList();
public TestBean() {
initList();
}
public TestBean(TestBean bean) {
this.bean = bean;
initList();
}
private void initList() {
list.add("1");
list.add("2");
list.add("3");
list.add(null);
}
public TestBean getBean() {
return bean;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public String encode(String data) {
return "!" + data;
}
public int getMaximum() {
return maximum;
}
public void setMaximum(int maximum) {
this.maximum = maximum;
}
public List getList() {
return list;
}
}

View File

@@ -0,0 +1,222 @@
/*
* Copyright 2004-2010 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 junit.framework.TestCase;
import org.springframework.binding.expression.EvaluationException;
import org.springframework.binding.expression.Expression;
import org.springframework.binding.expression.ExpressionVariable;
import org.springframework.binding.expression.ValueCoercionException;
import org.springframework.binding.expression.el.ELExpressionParser;
import org.springframework.binding.expression.el.ELExpressionParserTests;
import org.springframework.binding.expression.el.TestBean;
import org.springframework.binding.expression.support.FluentParserContext;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.expression.AccessException;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.PropertyAccessor;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.standard.SpelExpressionParser;
/**
* <p>
* Test cases to verify that {@link SpringELExpressionParser} matches to the functionality of {@link ELExpressionParser}
* as demonstrated in {@link ELExpressionParserTests}.
* </p>
*
* @author Rossen Stoyanchev
*/
public class ELExpressionParserCompatibilityTests extends TestCase {
private SpringELExpressionParser parser = new SpringELExpressionParser(new SpelExpressionParser());
protected void setUp() throws Exception {
parser.addPropertyAccessor(new SpecialPropertyAccessor());
}
public void testParseSimpleEvalExpressionNoParserContext() {
String expressionString = "3 + 4";
Expression exp = parser.parseExpression(expressionString, null);
assertEquals(new Integer(7), exp.getValue(null)); // Unified EL returns Long
}
public void testParseNullExpressionString() {
String expressionString = null;
try {
parser.parseExpression(expressionString, null);
fail("should have thrown iae");
} catch (IllegalArgumentException e) {
}
}
public void testParseNull() {
Expression exp = parser.parseExpression("null", null);
assertEquals(null, exp.getValue(null));
}
public void testParseEmptyExpressionString() {
String expressionString = "";
try {
parser.parseExpression(expressionString, null);
fail("should have thrown iae");
} catch (IllegalArgumentException e) {
}
}
public void testParseSimpleEvalExpressionNoEvalContextWithTypeCoersion() {
String expressionString = "3 + 4";
Expression exp = parser.parseExpression(expressionString, new FluentParserContext().expectResult(Long.class));
assertEquals(new Long(7), exp.getValue(null));
}
public void testParseBeanEvalExpressionNoParserContext() {
String expressionString = "value";
Expression exp = parser.parseExpression(expressionString, null);
assertEquals("foo", exp.getValue(new TestBean()));
}
public void testParseEvalExpressionWithContextTypeCoersion() {
String expressionString = "maximum";
Expression exp = parser
.parseExpression(expressionString, new FluentParserContext().expectResult(Integer.class));
assertEquals(new Integer(2), exp.getValue(new TestBean()));
}
public void testParseEvalExpressionWithContextCustomELVariableResolver() {
String expressionString = "specialProperty";
Expression exp = parser.parseExpression(expressionString, new FluentParserContext().evaluate(TestBean.class));
assertEquals("Custom resolver resolved this special property!", exp.getValue(new TestBean()));
}
public void testParseBeanEvalExpressionInvalidELVariable() {
try {
String expressionString = "bogus";
Expression exp = parser.parseExpression(expressionString, new FluentParserContext()
.evaluate(TestBean.class));
exp.getValue(new TestBean());
fail("Should have failed");
} catch (EvaluationException e) {
}
}
public void testParseLiteralExpression() {
String expressionString = "'value'";
Expression exp = parser.parseExpression(expressionString, null);
assertEquals("value", exp.getValue(null));
}
public void testParseTemplateExpression() {
String expressionString = "text text text #{value} text text text#{value}";
Expression exp = parser.parseExpression(expressionString, new FluentParserContext().template());
TestBean target = new TestBean();
assertEquals("text text text foo text text textfoo", exp.getValue(target));
}
public void testParseTemplateExpressionWithVariables() {
String expressionString = "#{value}#{#max}";
Expression exp = parser.parseExpression(expressionString, new FluentParserContext().template().variable(
new ExpressionVariable("max", "maximum")));
TestBean target = new TestBean();
assertEquals("foo2", exp.getValue(target)); // TODO:
}
public void testGetExpressionString() {
String expressionString = "maximum";
Expression exp = parser.parseExpression(expressionString, null);
assertEquals("maximum", exp.getExpressionString());
}
public void testGetExpressionType() {
String expressionString = "maximum";
Expression exp = parser.parseExpression(expressionString, null);
TestBean context = new TestBean();
assertEquals(int.class, exp.getValueType(context));
}
public void testGetValueWithCoersion() {
String expressionString = "maximum";
Expression exp = parser.parseExpression(expressionString, new FluentParserContext().expectResult(String.class));
TestBean context = new TestBean();
assertEquals("2", exp.getValue(context));
}
public void testGetValueCoersionError() {
String expressionString = "maximum";
Expression exp = parser.parseExpression(expressionString, new FluentParserContext()
.expectResult(TestBean.class));
TestBean context = new TestBean();
try {
exp.getValue(context);
fail("Should have failed with coersion");
} catch (ValueCoercionException e) {
}
}
public void testSetValue() {
String expressionString = "maximum";
Expression exp = parser.parseExpression(expressionString, null);
TestBean context = new TestBean();
exp.setValue(context, new Integer(5));
assertEquals(5, context.getMaximum());
}
public void testSetValueWithTypeCoersion() {
String expressionString = "maximum";
Expression exp = parser.parseExpression(expressionString, null);
TestBean context = new TestBean();
exp.setValue(context, "5");
assertEquals(5, context.getMaximum());
}
public void testSetValueCoersionError() {
String expressionString = "maximum";
Expression exp = parser.parseExpression(expressionString, null);
TestBean context = new TestBean();
try {
exp.setValue(context, "bogus");
fail("Should have failed with coersion");
} catch (ValueCoercionException e) {
}
}
private final class SpecialPropertyAccessor implements PropertyAccessor {
public void write(EvaluationContext context, Object target, String name, Object newValue)
throws AccessException {
}
public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException {
return new TypedValue("Custom resolver resolved this special property!", TypeDescriptor
.valueOf(String.class));
}
public Class[] getSpecificTargetClasses() {
return null;
}
public boolean canWrite(EvaluationContext context, Object target, String name) throws AccessException {
return false;
}
public boolean canRead(EvaluationContext context, Object target, String name) throws AccessException {
return "specialProperty".equals(name);
}
}
}