/*
* Copyright 2002-2016 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;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.expression.AccessException;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.Expression;
import org.springframework.expression.PropertyAccessor;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.standard.SpelExpression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import static org.junit.Assert.*;
///CLOVER:OFF
/**
* Tests accessing of properties.
*
* @author Andy Clement
*/
public class PropertyAccessTests extends AbstractExpressionTests {
@Test
public void testSimpleAccess01() {
evaluate("name", "Nikola Tesla", String.class);
}
@Test
public void testSimpleAccess02() {
evaluate("placeOfBirth.city", "SmilJan", String.class);
}
@Test
public void testSimpleAccess03() {
evaluate("stringArrayOfThreeItems.length", "3", Integer.class);
}
@Test
public void testNonExistentPropertiesAndMethods() {
// madeup does not exist as a property
evaluateAndCheckError("madeup", SpelMessage.PROPERTY_OR_FIELD_NOT_READABLE, 0);
// name is ok but foobar does not exist:
evaluateAndCheckError("name.foobar", SpelMessage.PROPERTY_OR_FIELD_NOT_READABLE, 5);
}
/**
* The standard reflection resolver cannot find properties on null objects but some
* supplied resolver might be able to - so null shouldn't crash the reflection resolver.
*/
@Test
public void testAccessingOnNullObject() throws Exception {
SpelExpression expr = (SpelExpression)parser.parseExpression("madeup");
EvaluationContext context = new StandardEvaluationContext(null);
try {
expr.getValue(context);
fail("Should have failed - default property resolver cannot resolve on null");
}
catch (Exception ex) {
checkException(ex, SpelMessage.PROPERTY_OR_FIELD_NOT_READABLE_ON_NULL);
}
assertFalse(expr.isWritable(context));
try {
expr.setValue(context,"abc");
fail("Should have failed - default property resolver cannot resolve on null");
}
catch (Exception ex) {
checkException(ex, SpelMessage.PROPERTY_OR_FIELD_NOT_WRITABLE_ON_NULL);
}
}
private void checkException(Exception e, SpelMessage expectedMessage) {
if (e instanceof SpelEvaluationException) {
SpelMessage sm = ((SpelEvaluationException)e).getMessageCode();
assertEquals("Expected exception type did not occur",expectedMessage,sm);
}
else {
fail("Should be a SpelException "+e);
}
}
@Test
// Adding a new property accessor just for a particular type
public void testAddingSpecificPropertyAccessor() throws Exception {
SpelExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext ctx = new StandardEvaluationContext();
// Even though this property accessor is added after the reflection one, it specifically
// names the String class as the type it is interested in so is chosen in preference to
// any 'default' ones
ctx.addPropertyAccessor(new StringyPropertyAccessor());
Expression expr = parser.parseRaw("new String('hello').flibbles");
Integer i = expr.getValue(ctx, Integer.class);
assertEquals((int) i, 7);
// The reflection one will be used for other properties...
expr = parser.parseRaw("new String('hello').CASE_INSENSITIVE_ORDER");
Object o = expr.getValue(ctx);
assertNotNull(o);
expr = parser.parseRaw("new String('hello').flibbles");
expr.setValue(ctx, 99);
i = expr.getValue(ctx, Integer.class);
assertEquals((int) i, 99);
// Cannot set it to a string value
try {
expr.setValue(ctx, "not allowed");
fail("Should not have been allowed");
}
catch (EvaluationException ex) {
// success - message will be: EL1063E:(pos 20): A problem occurred whilst attempting to set the property
// 'flibbles': 'Cannot set flibbles to an object of type 'class java.lang.String''
// System.out.println(e.getMessage());
}
}
@Test
public void testAddingRemovingAccessors() {
StandardEvaluationContext ctx = new StandardEvaluationContext();
// reflective property accessor is the only one by default
List<PropertyAccessor> propertyAccessors = ctx.getPropertyAccessors();
assertEquals(1,propertyAccessors.size());
StringyPropertyAccessor spa = new StringyPropertyAccessor();
ctx.addPropertyAccessor(spa);
assertEquals(2,ctx.getPropertyAccessors().size());
List<PropertyAccessor> copy = new ArrayList<>();
copy.addAll(ctx.getPropertyAccessors());
assertTrue(ctx.removePropertyAccessor(spa));
assertFalse(ctx.removePropertyAccessor(spa));
assertEquals(1,ctx.getPropertyAccessors().size());
ctx.setPropertyAccessors(copy);
assertEquals(2,ctx.getPropertyAccessors().size());
}
@Test
public void testAccessingPropertyOfClass() throws Exception {
Expression expression = parser.parseExpression("name");
Object value = expression.getValue(new StandardEvaluationContext(String.class));
assertEquals(value, "java.lang.String");
}
// This can resolve the property 'flibbles' on any String (very useful...)
private static class StringyPropertyAccessor implements PropertyAccessor {
int flibbles = 7;
@Override
public Class<?>[] getSpecificTargetClasses() {
return new Class[] {String.class};
}
@Override
public boolean canRead(EvaluationContext context, Object target, String name) throws AccessException {
if (!(target instanceof String)) {
throw new RuntimeException("Assertion Failed! target should be String");
}
return (name.equals("flibbles"));
}
@Override
public boolean canWrite(EvaluationContext context, Object target, String name) throws AccessException {
if (!(target instanceof String)) {
throw new RuntimeException("Assertion Failed! target should be String");
}
return (name.equals("flibbles"));
}
@Override
public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException {
if (!name.equals("flibbles")) {
throw new RuntimeException("Assertion Failed! name should be flibbles");
}
return new TypedValue(flibbles);
}
@Override
public void write(EvaluationContext context, Object target, String name, Object newValue) throws AccessException {
if (!name.equals("flibbles")) {
throw new RuntimeException("Assertion Failed! name should be flibbles");
}
try {
flibbles = (Integer) context.getTypeConverter().convertValue(newValue, TypeDescriptor.forObject(newValue), TypeDescriptor.valueOf(Integer.class));
}
catch (EvaluationException ex) {
throw new AccessException("Cannot set flibbles to an object of type '" + newValue.getClass() + "'");
}
}
}
}