View Javadoc
1   /*
2    * Copyright 2002-2012 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.expression.spel;
18  
19  import java.awt.Color;
20  import java.util.ArrayList;
21  import java.util.Arrays;
22  import java.util.HashMap;
23  import java.util.List;
24  import java.util.Map;
25  
26  import org.junit.Test;
27  
28  import org.springframework.expression.AccessException;
29  import org.springframework.expression.EvaluationContext;
30  import org.springframework.expression.EvaluationException;
31  import org.springframework.expression.Expression;
32  import org.springframework.expression.ParseException;
33  import org.springframework.expression.PropertyAccessor;
34  import org.springframework.expression.TypedValue;
35  import org.springframework.expression.spel.standard.SpelExpressionParser;
36  import org.springframework.expression.spel.support.StandardEvaluationContext;
37  
38  import static org.junit.Assert.*;
39  
40  ///CLOVER:OFF
41  
42  /**
43   * Testcases showing the common scenarios/use-cases for picking up the expression language support.
44   * The first test shows very basic usage, just drop it in and go.  By 'standard infrastructure', it means:<br>
45   * <ul>
46   * <li>The context classloader is used (so, the default classpath)
47   * <li>Some basic type converters are included
48   * <li>properties/methods/constructors are discovered and invoked using reflection
49   * </ul>
50   * The scenarios after that then how how to plug in extensions:<br>
51   * <ul>
52   * <li>Adding entries to the classpath that will be used to load types and define well known 'imports'
53   * <li>Defining variables that are then accessible in the expression
54   * <li>Changing the root context object against which non-qualified references are resolved
55   * <li>Registering java methods as functions callable from the expression
56   * <li>Adding a basic property resolver
57   * <li>Adding an advanced (better performing) property resolver
58   * <li>Adding your own type converter to support conversion between any types you like
59   * </ul>
60   *
61   * @author Andy Clement
62   */
63  public class ExpressionLanguageScenarioTests extends AbstractExpressionTests {
64  
65  	/**
66  	 * Scenario: using the standard infrastructure and running simple expression evaluation.
67  	 */
68  	@Test
69  	public void testScenario_UsingStandardInfrastructure() {
70  		try {
71  			// Create a parser
72  			SpelExpressionParser parser = new SpelExpressionParser();
73  			// Parse an expression
74  			Expression expr = parser.parseRaw("new String('hello world')");
75  			// Evaluate it using a 'standard' context
76  			Object value = expr.getValue();
77  			// They are reusable
78  			value = expr.getValue();
79  
80  			assertEquals("hello world", value);
81  			assertEquals(String.class, value.getClass());
82  		} catch (EvaluationException ee) {
83  			ee.printStackTrace();
84  			fail("Unexpected Exception: " + ee.getMessage());
85  		} catch (ParseException pe) {
86  			pe.printStackTrace();
87  			fail("Unexpected Exception: " + pe.getMessage());
88  		}
89  	}
90  
91  	/**
92  	 * Scenario: using the standard context but adding your own variables
93  	 */
94  	@Test
95  	public void testScenario_DefiningVariablesThatWillBeAccessibleInExpressions() throws Exception {
96  		// Create a parser
97  		SpelExpressionParser parser = new SpelExpressionParser();
98  		// Use the standard evaluation context
99  		StandardEvaluationContext ctx = new StandardEvaluationContext();
100 		ctx.setVariable("favouriteColour","blue");
101 		List<Integer> primes = new ArrayList<Integer>();
102 		primes.addAll(Arrays.asList(2,3,5,7,11,13,17));
103 		ctx.setVariable("primes",primes);
104 
105 		Expression expr = parser.parseRaw("#favouriteColour");
106 		Object value = expr.getValue(ctx);
107 		assertEquals("blue", value);
108 
109 		expr = parser.parseRaw("#primes.get(1)");
110 		value = expr.getValue(ctx);
111 		assertEquals(3, value);
112 
113 		// all prime numbers > 10 from the list (using selection ?{...})
114 		expr = parser.parseRaw("#primes.?[#this>10]");
115 		value = expr.getValue(ctx);
116 		assertEquals("[11, 13, 17]", value.toString());
117 	}
118 
119 
120 	static class TestClass {
121 		public String str;
122 		private int property;
123 		public int getProperty() { return property; }
124 		public void setProperty(int i) { property = i; }
125 	}
126 
127 	/**
128 	 * Scenario: using your own root context object
129 	 */
130 	@Test
131 	public void testScenario_UsingADifferentRootContextObject() throws Exception {
132 		// Create a parser
133 		SpelExpressionParser parser = new SpelExpressionParser();
134 		// Use the standard evaluation context
135 		StandardEvaluationContext ctx = new StandardEvaluationContext();
136 
137 		TestClass tc = new TestClass();
138 		tc.setProperty(42);
139 		tc.str = "wibble";
140 		ctx.setRootObject(tc);
141 
142 		// read it, set it, read it again
143 		Expression expr = parser.parseRaw("str");
144 		Object value = expr.getValue(ctx);
145 		assertEquals("wibble", value);
146 		expr = parser.parseRaw("str");
147 		expr.setValue(ctx, "wobble");
148 		expr = parser.parseRaw("str");
149 		value = expr.getValue(ctx);
150 		assertEquals("wobble", value);
151 		// or using assignment within the expression
152 		expr = parser.parseRaw("str='wabble'");
153 		value = expr.getValue(ctx);
154 		expr = parser.parseRaw("str");
155 		value = expr.getValue(ctx);
156 		assertEquals("wabble", value);
157 
158 		// private property will be accessed through getter()
159 		expr = parser.parseRaw("property");
160 		value = expr.getValue(ctx);
161 		assertEquals(42, value);
162 
163 		// ... and set through setter
164 		expr = parser.parseRaw("property=4");
165 		value = expr.getValue(ctx);
166 		expr = parser.parseRaw("property");
167 		value = expr.getValue(ctx);
168 		assertEquals(4,value);
169 	}
170 
171 	public static String repeat(String s) { return s+s; }
172 
173 	/**
174 	 * Scenario: using your own java methods and calling them from the expression
175 	 */
176 	@Test
177 	public void testScenario_RegisteringJavaMethodsAsFunctionsAndCallingThem() throws SecurityException, NoSuchMethodException {
178 		try {
179 			// Create a parser
180 			SpelExpressionParser parser = new SpelExpressionParser();
181 			// Use the standard evaluation context
182 			StandardEvaluationContext ctx = new StandardEvaluationContext();
183 			ctx.registerFunction("repeat",ExpressionLanguageScenarioTests.class.getDeclaredMethod("repeat",String.class));
184 
185 			Expression expr = parser.parseRaw("#repeat('hello')");
186 			Object value = expr.getValue(ctx);
187 			assertEquals("hellohello", value);
188 
189 		} catch (EvaluationException ee) {
190 			ee.printStackTrace();
191 			fail("Unexpected Exception: " + ee.getMessage());
192 		} catch (ParseException pe) {
193 			pe.printStackTrace();
194 			fail("Unexpected Exception: " + pe.getMessage());
195 		}
196 	}
197 
198 	/**
199 	 * Scenario: add a property resolver that will get called in the resolver chain, this one only supports reading.
200 	 */
201 	@Test
202 	public void testScenario_AddingYourOwnPropertyResolvers_1() throws Exception {
203 		// Create a parser
204 		SpelExpressionParser parser = new SpelExpressionParser();
205 		// Use the standard evaluation context
206 		StandardEvaluationContext ctx = new StandardEvaluationContext();
207 
208 		ctx.addPropertyAccessor(new FruitColourAccessor());
209 		Expression expr = parser.parseRaw("orange");
210 		Object value = expr.getValue(ctx);
211 		assertEquals(Color.orange, value);
212 
213 		try {
214 			expr.setValue(ctx, Color.blue);
215 			fail("Should not be allowed to set oranges to be blue !");
216 		} catch (SpelEvaluationException ee) {
217 			assertEquals(ee.getMessageCode(), SpelMessage.PROPERTY_OR_FIELD_NOT_WRITABLE_ON_NULL);
218 		}
219 	}
220 
221 	@Test
222 	public void testScenario_AddingYourOwnPropertyResolvers_2() throws Exception {
223 		// Create a parser
224 		SpelExpressionParser parser = new SpelExpressionParser();
225 		// Use the standard evaluation context
226 		StandardEvaluationContext ctx = new StandardEvaluationContext();
227 
228 		ctx.addPropertyAccessor(new VegetableColourAccessor());
229 		Expression expr = parser.parseRaw("pea");
230 		Object value = expr.getValue(ctx);
231 		assertEquals(Color.green, value);
232 
233 		try {
234 			expr.setValue(ctx, Color.blue);
235 			fail("Should not be allowed to set peas to be blue !");
236 		}
237 		catch (SpelEvaluationException ee) {
238 			assertEquals(ee.getMessageCode(), SpelMessage.PROPERTY_OR_FIELD_NOT_WRITABLE_ON_NULL);
239 		}
240 	}
241 
242 
243 	/**
244 	 * Regardless of the current context object, or root context object, this resolver can tell you what colour a fruit is !
245 	 * It only supports property reading, not writing.  To support writing it would need to override canWrite() and write()
246 	 */
247 	private static class FruitColourAccessor implements PropertyAccessor {
248 
249 		private static Map<String,Color> propertyMap = new HashMap<String,Color>();
250 
251 		static {
252 			propertyMap.put("banana",Color.yellow);
253 			propertyMap.put("apple",Color.red);
254 			propertyMap.put("orange",Color.orange);
255 		}
256 
257 		/**
258 		 * Null means you might be able to read any property, if an earlier property resolver hasn't beaten you to it
259 		 */
260 		@Override
261 		public Class<?>[] getSpecificTargetClasses() {
262 			return null;
263 		}
264 
265 		@Override
266 		public boolean canRead(EvaluationContext context, Object target, String name) throws AccessException {
267 			return propertyMap.containsKey(name);
268 		}
269 
270 		@Override
271 		public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException {
272 			return new TypedValue(propertyMap.get(name));
273 		}
274 
275 		@Override
276 		public boolean canWrite(EvaluationContext context, Object target, String name) throws AccessException {
277 			return false;
278 		}
279 
280 		@Override
281 		public void write(EvaluationContext context, Object target, String name, Object newValue)
282 				throws AccessException {
283 		}
284 
285 	}
286 
287 
288 	/**
289 	 * Regardless of the current context object, or root context object, this resolver can tell you what colour a vegetable is !
290 	 * It only supports property reading, not writing.
291 	 */
292 	private static class VegetableColourAccessor implements PropertyAccessor {
293 
294 		private static Map<String,Color> propertyMap = new HashMap<String,Color>();
295 
296 		static {
297 			propertyMap.put("carrot",Color.orange);
298 			propertyMap.put("pea",Color.green);
299 		}
300 
301 		/**
302 		 * Null means you might be able to read any property, if an earlier property resolver hasn't beaten you to it
303 		 */
304 		@Override
305 		public Class<?>[] getSpecificTargetClasses() {
306 			return null;
307 		}
308 
309 		@Override
310 		public boolean canRead(EvaluationContext context, Object target, String name) throws AccessException {
311 			return propertyMap.containsKey(name);
312 		}
313 
314 		@Override
315 		public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException {
316 			return new TypedValue(propertyMap.get(name));
317 		}
318 
319 		@Override
320 		public boolean canWrite(EvaluationContext context, Object target, String name) throws AccessException {
321 			return false;
322 		}
323 
324 		@Override
325 		public void write(EvaluationContext context, Object target, String name, Object newValue) throws AccessException {
326 		}
327 
328 	}
329 }