1 package org.cateproject.view.velocity;
2
3 import java.beans.PropertyEditor;
4 import java.io.IOException;
5 import java.io.Writer;
6 import java.util.Arrays;
7 import java.util.List;
8 import java.util.Map;
9
10 import org.apache.velocity.context.Context;
11 import org.apache.velocity.context.InternalContextAdapter;
12 import org.apache.velocity.exception.MethodInvocationException;
13 import org.apache.velocity.exception.ParseErrorException;
14 import org.apache.velocity.exception.ResourceNotFoundException;
15 import org.apache.velocity.runtime.parser.node.Node;
16 import org.springframework.beans.BeanWrapper;
17 import org.springframework.beans.BeansException;
18 import org.springframework.beans.InvalidPropertyException;
19 import org.springframework.beans.PropertyAccessorFactory;
20 import org.springframework.context.ApplicationContext;
21 import org.springframework.context.ApplicationContextAware;
22 import org.springframework.context.NoSuchMessageException;
23 import org.springframework.core.convert.ConversionService;
24 import org.springframework.util.StringUtils;
25 import org.springframework.validation.BindingResult;
26 import org.springframework.validation.Errors;
27 import org.springframework.validation.ObjectError;
28 import org.springframework.web.servlet.support.RequestContext;
29 import org.springframework.web.servlet.view.AbstractTemplateView;
30 import org.springframework.web.util.HtmlUtils;
31
32 public class CateSpringBindDirective extends CateBlockDirective {
33
34 public CateSpringBindDirective() {
35 super("path");
36 }
37
38 @Override
39 public String getName() {
40 return "springBind";
41 }
42
43 @Override
44 public boolean render(InternalContextAdapter context, Writer writer,
45 Node node) throws IOException, ResourceNotFoundException,
46 ParseErrorException, MethodInvocationException {
47 String path = getArgument(context, node, 0);
48 RequestContext requestContext = (RequestContext)context.get(AbstractTemplateView.SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE);
49 ConversionService conversionService = (ConversionService) context.get(ApplicationContextAwareVelocityToolboxView.CONVERSION_SERVICE_ATTRIBUTE);
50
51 try {
52 context.put("status", requestContext.getBindStatus(path));
53 node.jjtGetChild(1).render(context, writer);
54 } catch(IndexOutOfBoundsException ioobe) {
55 org.springframework.web.servlet.support.BindStatus status = new org.springframework.web.servlet.support.BindStatus(requestContext, path, true);
56 context.put("status", status);
57 node.jjtGetChild(1).render(context, writer);
58 } catch(InvalidPropertyException ipe) {
59 BindStatus status = new BindStatus(requestContext, context, path, true,conversionService);
60 context.put("status", status);
61 node.jjtGetChild(1).render(context, writer);
62 }
63
64
65
66
67
68 return true;
69
70 }
71
72 class BindStatus {
73
74 private final RequestContext requestContext;
75
76 private final String path;
77
78 private final boolean htmlEscape;
79
80 private final String expression;
81
82 private final Errors errors;
83
84 private BindingResult bindingResult;
85
86 private Object value;
87
88 private Class valueType;
89
90 private Object actualValue;
91
92 private PropertyEditor editor;
93
94 private List objectErrors;
95
96 private String[] errorCodes;
97
98 private String[] errorMessages;
99
100
101
102
103
104
105
106
107
108
109 public BindStatus(RequestContext requestContext, Context context, String path, boolean htmlEscape, ConversionService conversionService)
110 throws IllegalStateException {
111
112 this.requestContext = requestContext;
113 this.path = path;
114 this.htmlEscape = htmlEscape;
115
116
117 String beanName = null;
118 int dotPos = path.indexOf('.');
119 if (dotPos == -1) {
120
121 beanName = path;
122 this.expression = null;
123 }
124 else {
125 beanName = path.substring(0, dotPos);
126 this.expression = path.substring(dotPos + 1);
127 }
128
129 this.errors = requestContext.getErrors(beanName, false);
130
131 if (this.errors != null) {
132
133
134
135 if (this.expression != null) {
136 if ("*".equals(this.expression)) {
137 this.objectErrors = this.errors.getAllErrors();
138 }
139 else if (this.expression.endsWith("*")) {
140 this.objectErrors = this.errors.getFieldErrors(this.expression);
141 }
142 else {
143 this.objectErrors = this.errors.getFieldErrors(this.expression);
144 this.value = this.errors.getFieldValue(this.expression);
145 this.valueType = this.errors.getFieldType(this.expression);
146 if (this.errors instanceof BindingResult) {
147 this.bindingResult = (BindingResult) this.errors;
148 this.actualValue = this.bindingResult.getRawFieldValue(this.expression);
149 this.editor = this.bindingResult.findEditor(this.expression, null);
150 }
151 }
152 }
153 else {
154 this.objectErrors = this.errors.getGlobalErrors();
155 }
156 initErrorCodes();
157 }
158
159 else {
160
161
162
163 Object target = getModelObject(requestContext.getModel(),context,beanName);
164 if (target == null) {
165 throw new IllegalStateException("Neither BindingResult nor plain target object for bean name '" +
166 beanName + "' available as request attribute");
167 }
168 if (this.expression != null && !"*".equals(this.expression) && !this.expression.endsWith("*")) {
169 BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(target);
170 bw.setConversionService(conversionService);
171 this.valueType = bw.getPropertyType(this.expression);
172 this.value = bw.getPropertyValue(this.expression);
173 }
174 this.errorCodes = new String[0];
175 this.errorMessages = new String[0];
176 }
177
178 if (htmlEscape && this.value instanceof String) {
179 this.value = HtmlUtils.htmlEscape((String) this.value);
180 }
181 }
182
183
184 private Object getModelObject(Map<String, Object> model, Context context, String modelName) {
185 if (model != null) {
186 return model.get(modelName);
187 } else {
188 return context.get(modelName);
189 }
190 }
191
192
193
194
195 private void initErrorCodes() {
196 this.errorCodes = new String[this.objectErrors.size()];
197 for (int i = 0; i < this.objectErrors.size(); i++) {
198 ObjectError error = (ObjectError) this.objectErrors.get(i);
199 this.errorCodes[i] = error.getCode();
200 }
201 }
202
203
204
205
206 private void initErrorMessages() throws NoSuchMessageException {
207 if (this.errorMessages == null) {
208 this.errorMessages = new String[this.objectErrors.size()];
209 for (int i = 0; i < this.objectErrors.size(); i++) {
210 ObjectError error = (ObjectError) this.objectErrors.get(i);
211 this.errorMessages[i] = this.requestContext.getMessage(error, this.htmlEscape);
212 }
213 }
214 }
215
216
217
218
219
220
221 public String getPath() {
222 return this.path;
223 }
224
225
226
227
228
229
230
231
232 public String getExpression() {
233 return this.expression;
234 }
235
236
237
238
239
240
241
242 public Object getValue() {
243 return this.value;
244 }
245
246
247
248
249
250
251 public Class getValueType() {
252 return this.valueType;
253 }
254
255
256
257
258
259 public Object getActualValue() {
260 return this.actualValue;
261 }
262
263
264
265
266
267
268
269
270 public String getDisplayValue() {
271 if (this.value instanceof String) {
272 return (String) this.value;
273 }
274 if (this.value != null) {
275 return (this.htmlEscape ? HtmlUtils.htmlEscape(this.value.toString()) : this.value.toString());
276 }
277 return "";
278 }
279
280
281
282
283 public boolean isError() {
284 return (this.errorCodes != null && this.errorCodes.length > 0);
285 }
286
287
288
289
290
291 public String[] getErrorCodes() {
292 return this.errorCodes;
293 }
294
295
296
297
298 public String getErrorCode() {
299 return (this.errorCodes.length > 0 ? this.errorCodes[0] : "");
300 }
301
302
303
304
305
306 public String[] getErrorMessages() {
307 initErrorMessages();
308 return this.errorMessages;
309 }
310
311
312
313
314 public String getErrorMessage() {
315 initErrorMessages();
316 return (this.errorMessages.length > 0 ? this.errorMessages[0] : "");
317 }
318
319
320
321
322
323
324
325 public String getErrorMessagesAsString(String delimiter) {
326 initErrorMessages();
327 return StringUtils.arrayToDelimitedString(this.errorMessages, delimiter);
328 }
329
330
331
332
333
334
335
336 public Errors getErrors() {
337 return this.errors;
338 }
339
340
341
342
343
344
345 public PropertyEditor getEditor() {
346 return this.editor;
347 }
348
349
350
351
352
353
354
355 public PropertyEditor findEditor(Class valueClass) {
356 return (this.bindingResult != null ? this.bindingResult.findEditor(this.expression, valueClass) : null);
357 }
358
359
360 @Override
361 public String toString() {
362 StringBuilder sb = new StringBuilder("BindStatus: ");
363 sb.append("expression=[").append(this.expression).append("]; ");
364 sb.append("value=[").append(this.value).append("]");
365 if (isError()) {
366 sb.append("; errorCodes=").append(Arrays.asList(this.errorCodes));
367 }
368 return sb.toString();
369 }
370 }
371 }