1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.springmodules.validation.valang.parser;
18
19 import java.lang.reflect.Method;
20 import java.util.HashMap;
21 import java.util.List;
22 import java.util.Map;
23
24 import javassist.ClassPool;
25 import javassist.CtClass;
26 import javassist.CtMethod;
27 import javassist.CtNewMethod;
28
29 import org.apache.commons.collections.Predicate;
30 import org.slf4j.Logger;
31 import org.slf4j.LoggerFactory;
32 import org.springframework.beans.BeansException;
33 import org.springframework.context.ApplicationContext;
34 import org.springframework.context.ApplicationContextAware;
35 import org.springframework.util.ClassUtils;
36 import org.springframework.util.ReflectionUtils;
37 import org.springframework.util.StringUtils;
38 import org.springmodules.validation.util.PrimitiveClassUtils;
39 import org.springmodules.validation.util.date.DefaultDateParser;
40 import org.springmodules.validation.valang.ValangException;
41 import org.springmodules.validation.valang.functions.AbstractInitializableFunction;
42 import org.springmodules.validation.valang.functions.BeanPropertyFunction;
43 import org.springmodules.validation.valang.functions.EmailFunction;
44 import org.springmodules.validation.valang.functions.Function;
45 import org.springmodules.validation.valang.functions.InRoleFunction;
46 import org.springmodules.validation.valang.functions.LengthOfFunction;
47 import org.springmodules.validation.valang.functions.LowerCaseFunction;
48 import org.springmodules.validation.valang.functions.NotFunction;
49 import org.springmodules.validation.valang.functions.RegExFunction;
50 import org.springmodules.validation.valang.functions.ResolveFunction;
51 import org.springmodules.validation.valang.functions.UpperCaseFunction;
52 import org.springmodules.validation.valang.predicates.BetweenTestPredicate;
53 import org.springmodules.validation.valang.predicates.EqualsTestPredicate;
54 import org.springmodules.validation.valang.predicates.GenericTestPredicate;
55 import org.springmodules.validation.valang.predicates.GreaterThanOrEqualTestPredicate;
56 import org.springmodules.validation.valang.predicates.GreaterThanTestPredicate;
57 import org.springmodules.validation.valang.predicates.InTestPredicate;
58 import org.springmodules.validation.valang.predicates.LessThanOrEqualTestPredicate;
59 import org.springmodules.validation.valang.predicates.LessThanTestPredicate;
60 import org.springmodules.validation.valang.predicates.NotBetweenTestPredicate;
61 import org.springmodules.validation.valang.predicates.NotEqualsTestPredicate;
62 import org.springmodules.validation.valang.predicates.NotInTestPredicate;
63 import org.springmodules.validation.valang.predicates.Operator;
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80 public class DefaultVisitor implements ApplicationContextAware, ValangVisitor {
81
82 final Logger logger = LoggerFactory.getLogger(DefaultVisitor.class);
83
84
85 final static Map<String, Function> hGeneratedBeanFunctions = new HashMap<String, Function>();
86
87
88
89
90
91 public static final char INDEXED_DELIM_BEGIN = '[';
92 public static final char INDEXED_DELIM_END = ']';
93
94 private ValangVisitor visitor = null;
95 private DefaultDateParser dateParser = null;
96 private ApplicationContext applicationContext = null;
97
98
99
100
101 public DefaultVisitor() {
102 this.dateParser = new DefaultDateParser();
103 }
104
105
106
107
108 public Function getFunction(String name, Function[] arguments, int line, int column) {
109 return doGetFunction(name, arguments, line, column);
110 }
111
112
113
114
115
116
117
118
119
120
121
122 public Function getFunction(String className, String propName) {
123 Function result = null;
124 String propertyName = propName;
125
126
127
128
129 if (!StringUtils.hasText(className) || propertyName.startsWith("this") ||
130 (propertyName.indexOf(INDEXED_DELIM_END) != -1 && (propName.length() > (propertyName.indexOf(INDEXED_DELIM_END) + 1))) ||
131 propertyName.toLowerCase().indexOf("jsonMessageString".toLowerCase()) != -1) {
132 result = new BeanPropertyFunction(propertyName);
133 } else {
134 String classFunctionName = null;
135 String property = null;
136 String reflectProperty = null;
137 String indexedKey = null;
138
139 boolean listOrMapProperty = propertyName.indexOf(INDEXED_DELIM_BEGIN) != -1;
140
141 if (listOrMapProperty) {
142 int indexedDelimEnd = propertyName.indexOf(INDEXED_DELIM_END);
143
144 indexedKey = propertyName.substring((propertyName.indexOf(INDEXED_DELIM_BEGIN) + 1), indexedDelimEnd);
145 propertyName = propertyName.substring(0, propertyName.indexOf(INDEXED_DELIM_BEGIN));
146 }
147
148 classFunctionName = className + org.apache.commons.lang.StringUtils.capitalize(propertyName) +
149 (indexedKey != null ? org.apache.commons.lang.StringUtils.capitalize(indexedKey) : "") +
150 "BeanPropertyFunction$$Valang";
151
152
153 String key = classFunctionName;
154
155 if (hGeneratedBeanFunctions.containsKey(key)) {
156 result = hGeneratedBeanFunctions.get(key);
157 } else {
158 try {
159 Class<?> clazz = ClassUtils.forName(className, ClassUtils.getDefaultClassLoader());
160 Class<?> methodReturnType = null;
161 boolean indexedProperty = propertyName.indexOf(".") != -1;
162
163 if (indexedProperty) {
164 String[] properties = org.apache.commons.lang.StringUtils.split(propertyName, ".");
165
166 int count = 1;
167 StringBuilder sb = new StringBuilder();
168 Class<?> nestedMethodReturnType = null;
169
170 for (String prop : properties) {
171 reflectProperty = "get" + org.apache.commons.lang.StringUtils.capitalize(prop);
172
173 sb.append(reflectProperty);
174 sb.append("()");
175
176 if (count < properties.length) {
177 sb.append(".");
178 }
179
180 if (nestedMethodReturnType == null) {
181 Method nestedMethod = ReflectionUtils.findMethod(clazz, reflectProperty);
182 nestedMethodReturnType = nestedMethod.getReturnType();
183 } else {
184 Method nestedMethod = ReflectionUtils.findMethod(nestedMethodReturnType, reflectProperty);
185 nestedMethodReturnType = nestedMethod.getReturnType();
186 }
187
188 count++;
189 }
190
191 property = sb.toString();
192
193 methodReturnType = nestedMethodReturnType;
194 } else {
195 StringBuilder sb = new StringBuilder();
196
197 sb.append("get");
198 sb.append(org.apache.commons.lang.StringUtils.capitalize(propertyName));
199
200 reflectProperty = sb.toString();
201
202 sb.append("()");
203
204 property = sb.toString();
205
206 methodReturnType = ReflectionUtils.findMethod(clazz, reflectProperty).getReturnType();
207 }
208
209 Class<?> primitiveClass = PrimitiveClassUtils.resolvePrimitiveClassName(methodReturnType);
210
211 ClassPool pool = ClassPool.getDefault();
212 CtClass cc = pool.makeClass(classFunctionName);
213
214 cc.addInterface(pool.get("org.springmodules.validation.valang.functions.Function"));
215
216 StringBuilder generatedMethod = new StringBuilder();
217 generatedMethod.append("public Object getResult(Object target) {");
218 generatedMethod.append(" return ");
219
220 if (primitiveClass != null) {
221 generatedMethod.append(" new " + primitiveClass.getName() + "(");
222 }
223
224 generatedMethod.append(" ((" + className + ")target).");
225
226 generatedMethod.append(property);
227
228 if (primitiveClass != null) {
229 generatedMethod.append(")");
230 } else if (listOrMapProperty &&
231 org.apache.commons.lang.ClassUtils.isAssignable(methodReturnType, Map.class)) {
232 generatedMethod.append(".get(\"" + indexedKey + "\")");
233 } else if (listOrMapProperty &&
234 org.apache.commons.lang.ClassUtils.isAssignable(methodReturnType, List.class)) {
235 generatedMethod.append(".get(" + indexedKey + ")");
236 } else if (methodReturnType.isArray()) {
237 int arrayIndex = Integer.parseInt(indexedKey);
238
239 generatedMethod.append("[" + arrayIndex + "]");
240 }
241
242 generatedMethod.append(";");
243 generatedMethod.append("}");
244
245 CtMethod m = CtNewMethod.make(generatedMethod.toString(), cc);
246 cc.addMethod(m);
247
248 result = (Function)cc.toClass().newInstance();
249
250 hGeneratedBeanFunctions.put(key, result);
251
252 logger.info("Generated bytecode for {}.{} as '{}'.",
253 new Object[] { className,
254 property + "()" + (indexedKey != null ? "[" + indexedKey + "]" : ""),
255 classFunctionName });
256 } catch (Exception e) {
257 logger.error("Problem generating bytecode for {}.{}. {}",
258 new Object[] { classFunctionName,
259 org.apache.commons.lang.StringUtils.capitalize(propertyName),
260 e.getMessage() });
261
262
263 result = new BeanPropertyFunction(propName);
264 }
265 }
266 }
267
268 return result;
269 }
270
271 private Function doGetFunction(String name, Function[] arguments, int line, int column) {
272
273 Function function = resolveCustomFunction(name, arguments, line, column);
274 if (function != null) {
275 return function;
276 }
277
278 function = resolveFunctionFromApplicationContext(name, arguments, line, column);
279 if (function != null) {
280 return function;
281 }
282
283 function = resolveDefaultFunction(name, arguments, line, column);
284 if (function != null) {
285 return function;
286 }
287
288 throw new ValangException("Could not find function [" + name + "]", line, column);
289 }
290
291 private Function resolveCustomFunction(String name, Function[] arguments, int line, int column) {
292 if (getVisitor() == null) {
293 return null;
294 }
295 return getVisitor().getFunction(name, arguments, line, column);
296 }
297
298 private Function resolveFunctionFromApplicationContext(String name, Function[] arguments, int line, int column) {
299 if (applicationContext == null || !applicationContext.containsBean(name)) {
300 return null;
301 }
302
303 Object bean = applicationContext.getBean(name);
304
305 if (!AbstractInitializableFunction.class.isInstance(bean)) {
306 logger.warn("Bean '" + name + "' is not of a '" + AbstractInitializableFunction.class.getName() + "' type");
307 return null;
308 }
309
310 AbstractInitializableFunction function = (AbstractInitializableFunction) bean;
311 function.init(arguments, line, column);
312
313 return function;
314 }
315
316 private Function resolveDefaultFunction(String name, Function[] arguments, int line, int column) {
317 if ("len".equals(name) || "length".equals(name) || "size".equals(name) || "count".equals(name)) {
318 return new LengthOfFunction(arguments, line, column);
319 } else if ("upper".equals(name)) {
320 return new UpperCaseFunction(arguments, line, column);
321 } else if ("lower".equals(name)) {
322 return new LowerCaseFunction(arguments, line, column);
323 } else if ("!".equals(name)) {
324 return new NotFunction(arguments, line, column);
325 } else if ("resolve".equals(name)) {
326 return new ResolveFunction(arguments, line, column);
327 } else if ("match".equals(name) || "matches".equals(name)) {
328 return new RegExFunction(arguments, line, column);
329 } else if ("inRole".equals(name)) {
330 return new InRoleFunction(arguments, line, column);
331 } else if ("email".equals(name)) {
332 return new EmailFunction(arguments, line, column);
333 }
334
335 return null;
336 }
337
338
339
340
341 public ValangVisitor getVisitor() {
342 return this.visitor;
343 }
344
345
346
347
348
349
350
351
352 public void setVisitor(ValangVisitor visitor) {
353 this.visitor = visitor;
354 }
355
356
357
358
359
360
361
362
363
364
365
366
367 public Predicate getPredicate(Function leftFunction, Operator operator, Function rightFunction, int line, int column) {
368 Predicate result = null;
369
370 switch (operator) {
371 case EQUAL:
372 result = new EqualsTestPredicate(leftFunction, operator, rightFunction, line, column);
373 break;
374 case NOT_EQUAL:
375 result = new NotEqualsTestPredicate(leftFunction, operator, rightFunction, line, column);
376 break;
377 case LESS_THAN:
378 result = new LessThanTestPredicate(leftFunction, operator, rightFunction, line, column);
379 break;
380 case LESS_THAN_OR_EQUAL:
381 result = new LessThanOrEqualTestPredicate(leftFunction, operator, rightFunction, line, column);
382 break;
383 case GREATER_THAN:
384 result = new GreaterThanTestPredicate(leftFunction, operator, rightFunction, line, column);
385 break;
386 case GREATER_THAN_OR_EQUAL:
387 result = new GreaterThanOrEqualTestPredicate(leftFunction, operator, rightFunction, line, column);
388 break;
389 case BETWEEN:
390 result = new BetweenTestPredicate(leftFunction, operator, rightFunction, line, column);
391 break;
392 case NOT_BETWEEN:
393 result = new NotBetweenTestPredicate(leftFunction, operator, rightFunction, line, column);
394 break;
395 case IN:
396 result = new InTestPredicate(leftFunction, operator, rightFunction, line, column);
397 break;
398 case NOT_IN:
399 result = new NotInTestPredicate(leftFunction, operator, rightFunction, line, column);
400 break;
401 default:
402 result = new GenericTestPredicate(leftFunction, operator, rightFunction, line, column);
403 break;
404 }
405
406 return result;
407 }
408
409
410
411
412 public DefaultDateParser getDateParser() {
413 return dateParser;
414 }
415
416
417
418
419 public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
420 this.applicationContext = applicationContext;
421 }
422
423 }