2009/05/20 - Apache Shale has been retired.
For more information, please explore the Attic.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.shale.validator.faces;
19
20 import java.io.IOException;
21 import java.util.ArrayList;
22 import java.util.Iterator;
23 import java.util.LinkedHashMap;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.Set;
27 import javax.faces.component.EditableValueHolder;
28 import javax.faces.component.UIComponent;
29 import javax.faces.component.UIComponentBase;
30 import javax.faces.component.UIForm;
31 import javax.faces.component.UINamingContainer;
32 import javax.faces.context.FacesContext;
33 import javax.faces.context.ResponseWriter;
34 import javax.faces.el.ValueBinding;
35 import org.apache.commons.validator.ValidatorAction;
36 import org.apache.commons.validator.Var;
37 import org.apache.shale.util.Tags;
38 import org.apache.shale.validator.CommonsValidator;
39
40 /***
41 * <p>A JSF component that encodes JavaScript for
42 * all client-side validations specified in the same
43 * JSP page (with <code>s:commonsValidator</code>.
44 *
45 * $Id: ValidatorScript.java 464373 2006-10-16 04:21:54Z rahul $
46 */
47 public class ValidatorScript extends UIComponentBase {
48
49
50
51
52
53 /***
54 * <p>The value of the <code>functionName</code> property.</p>
55 */
56 private String functionName;
57
58
59 /***
60 * <p>A map of validators, representing all of the Commons Validators
61 * attached to components in the current component hierarchy.
62 * The keys of the map are validator type names. The values are
63 * maps from IDs to CommonsValidator objects.</p>
64 */
65 private Map validators = new LinkedHashMap();
66
67
68 /***
69 * <p>Holds vars loaded with EL that are evaluated at render time.
70 * The key into the map is the component's client id.</p>
71 */
72 private Map validatorVars = new LinkedHashMap();
73
74
75 /***
76 * <p>The component renders itself; therefore, this
77 * method returns null.</p>
78 *
79 * @return <code>null</code> component handles renderering
80 */
81 public String getRendererType() {
82 return null;
83 }
84
85
86 /***
87 * <p>Returns the component's family. In this case,
88 * the component is not associated with a family,
89 * so this method returns null.</p>
90 *
91 * @return component's family
92 */
93 public String getFamily() {
94 return null;
95 }
96
97
98 /***
99 * <p>Return the value of the <code>functionName</code> property.</p>
100 *
101 * @return callback function from the form's onsubmit.
102 */
103 public String getFunctionName() {
104
105 if (this.functionName != null) {
106 return functionName;
107 }
108 ValueBinding _vb = getValueBinding("functionName");
109 if (_vb != null) {
110 return (String) _vb.getValue(getFacesContext());
111 } else {
112 return null;
113 }
114
115 }
116
117
118 /***
119 * <p>Set the value of the <code>functionName</code> property.</p>
120 *
121 * @param functionName The new function name
122 */
123 public void setFunctionName(String functionName) {
124 this.functionName = functionName;
125 }
126
127
128 /***
129 * <p>Restore the state of this component.</p>
130 *
131 * @param context FacesContext for the current request
132 * @param state State to be restored
133 */
134 public void restoreState(FacesContext context, Object state) {
135
136 Object values[] = (Object[]) state;
137 super.restoreState(context, values[0]);
138 this.functionName = (String) values[1];
139
140 }
141
142
143 /***
144 * <p>Save the state of this component.</p>
145 *
146 * @param context FacesContext for the current request
147 * @return component's state
148 */
149 public Object saveState(FacesContext context) {
150
151 Object values[] = new Object[2];
152 values[0] = super.saveState(context);
153 values[1] = this.functionName;
154 return values;
155
156 }
157
158
159 /***
160 * <p>Restructures the validator hierarchy grouping by
161 * form name, type and id.</p>
162 *
163 * @return validators grouped by form, type and clientId
164 */
165 private Map getValidatorsGroupByFormName() {
166
167 Map formValidators = new LinkedHashMap();
168
169 Iterator vi = validators.entrySet().iterator();
170 while (vi.hasNext()) {
171
172 Map.Entry typeEntry = (Map.Entry) vi.next();
173 Map typeMap = (Map) typeEntry.getValue();
174
175 String type = (String) typeEntry.getKey();
176
177 Iterator ti = typeMap.entrySet().iterator();
178 while (ti.hasNext()) {
179
180 Map.Entry idEntry = (Map.Entry) ti.next();
181 String id = (String) idEntry.getKey();
182 CommonsValidator v = (CommonsValidator) idEntry.getValue();
183 String formName = v.getFormName();
184
185 Map formTypeMap = (Map) formValidators.get(formName);
186 if (formTypeMap == null) {
187 formTypeMap = new LinkedHashMap();
188 formValidators.put(formName, formTypeMap);
189 }
190
191 Map formTypeIdMap = (Map) formTypeMap.get(type);
192 if (formTypeIdMap == null) {
193 formTypeIdMap = new LinkedHashMap();
194 formTypeMap.put(type, formTypeIdMap);
195 }
196
197 formTypeIdMap.put(id, v);
198 }
199
200 }
201
202 return formValidators;
203
204 }
205
206
207 /***
208 * <p>Registers a validator according to type and id.</p>
209 *
210 * @param type The type of the validator
211 * @param id The validator's identifier
212 * @param v The Commons validator associated with the id and type
213 */
214 private void addValidator(String type, String id, CommonsValidator v) {
215
216 Map map = (Map) validators.get(type);
217 if (map == null) {
218 map = new LinkedHashMap();
219 validators.put(type, map);
220 }
221 if (id != null) {
222 map.put(id, v);
223 }
224 }
225
226
227 /***
228 * <p>Recursively finds all Commons validators for the all of the
229 * components in a component hierarchy and adds them to a map.</p>
230 * <p>If a validator's type is required, this method sets the
231 * associated component's required property to true. This is
232 * necessary because JSF does not validate empty fields unless
233 * a component's required property is true.</p>
234 *
235 * @param c The component at the root of the component tree
236 * @param context The FacesContext for this request
237 */
238 private void findCommonsValidators(UIComponent c, FacesContext context) {
239 if (c instanceof EditableValueHolder && c.isRendered()) {
240 EditableValueHolder h = (EditableValueHolder) c;
241 javax.faces.validator.Validator[] vs = h.getValidators();
242 for (int i = 0; i < vs.length; i++) {
243 if (vs[i] instanceof CommonsValidator) {
244 CommonsValidator v = (CommonsValidator) vs[i];
245 v.setFormName(findForm(context, c));
246 if (Boolean.TRUE.equals(v.getClient())) {
247
248
249 Map clientIds = (Map) c.getAttributes().get(ValidatorInputRenderer.VALIDATOR_CLIENTIDS_ATTR);
250 if (clientIds != null) {
251
252 validatorVars.putAll(clientIds);
253 Iterator ci = clientIds.entrySet().iterator();
254 while (ci.hasNext()) {
255 Map.Entry e = (Map.Entry) ci.next();
256
257 String id = (String) e.getKey();
258 addValidator(v.getType(), id, v);
259
260 ValidatorAction action = v.getValidatorAction();
261 List list = action.getDependencyList();
262 Iterator iter = list.iterator();
263 while (iter.hasNext()) {
264 String type = (String) iter.next();
265 addValidator(type, id, v);
266 }
267
268 }
269
270 } else {
271
272 String id = c.getClientId(context);
273 addValidator(v.getType(), id, v);
274
275 ValidatorAction action = v.getValidatorAction();
276 List list = action.getDependencyList();
277 Iterator iter = list.iterator();
278 while (iter.hasNext()) {
279 String type = (String) iter.next();
280 addValidator(type, id, v);
281 }
282 }
283
284 }
285 if (Boolean.TRUE.equals(v.getServer())) {
286
287
288
289
290 if ("required".equals(v.getType())) {
291 h.setRequired(true);
292 }
293 }
294 }
295 }
296 }
297
298 Iterator childrenIterator = c.getFacetsAndChildren();
299 while (childrenIterator.hasNext()) {
300 UIComponent child = (UIComponent) childrenIterator.next();
301 findCommonsValidators(child, context);
302 }
303 childrenIterator = null;
304 }
305
306
307 /***
308 * <p>Write the start of the script for client-side validation.</p>
309 *
310 * @param writer A response writer
311 *
312 * @exception IOException if an input/output error occurs
313 */
314 private void writeScriptStart(ResponseWriter writer) throws IOException {
315 writer.startElement("script", this);
316 writer.writeAttribute("type", "text/javascript", null);
317 writer.writeAttribute("language", "Javascript1.1", null);
318 writer.write("\n");
319 }
320
321
322 /***
323 * <p>Write the end of the script for client-side validation.</p>
324 *
325 * @param writer A response writer
326 *
327 * @exception IOException if an input/output error occurs
328 */
329 private void writeScriptEnd(ResponseWriter writer) throws IOException {
330 writer.write("\n");
331 writer.endElement("script");
332 }
333
334
335 /***
336 * <p>Returns the name of the JavaScript function, specified in
337 * the JSP page (presumably), that validates this JSP page's form.</p>
338 *
339 * @param writer A response writer
340 * @param context The FacesContext for this request
341 *
342 * @exception IOException if an input/output error occurs
343 */
344 private void writeValidationFunctions(ResponseWriter writer,
345 FacesContext context) throws IOException {
346
347 StringBuffer buff = new StringBuffer();
348 buff.append("var bCancel = false;\n")
349 .append("function ")
350 .append(getAttributes().get("functionName").toString()).append("(form) {\n")
351 .append("\tvar bValid = true;\n")
352 .append("\tvar sFormName = jcv_retrieveFormName(form);\n");
353
354 Map formValidators = getValidatorsGroupByFormName();
355 Iterator formIter = formValidators.entrySet().iterator();
356 while (formIter.hasNext()) {
357 Map.Entry typeEntry = (Map.Entry) formIter.next();
358 String formName = (String) typeEntry.getKey();
359 Map formTypeValidators = (Map) typeEntry.getValue();
360
361
362
363 buff.append("\tif ((bValid && !bCancel && (\"")
364 .append(formName)
365 .append("\" == sFormName))) {\n")
366 .append("\t\tbValid = (");
367
368
369 Iterator iter = getTypesOrderedByDependencies(formTypeValidators.keySet()).iterator();
370 boolean first = true;
371 while (iter.hasNext()) {
372 String type = (String) iter.next();
373 ValidatorAction a = CommonsValidator.getValidatorAction(type);
374
375 buff.append((!first ? " && " : ""))
376 .append(a.getJsFunctionName())
377 .append("(form)");
378 first = false;
379
380 writer.write("function ");
381 StringBuffer callback = new StringBuffer();
382
383
384
385 String fnameMnemonic = CommonsValidator.getJsCallbackMnemonic(type);
386
387 callback.append(formName).append('_').append(fnameMnemonic);
388 writer.write(callback.toString());
389 writer.write("() { \n");
390
391 Map map = (Map) formTypeValidators.get(type);
392 Iterator iter2 = map.keySet().iterator();
393 int k = 0;
394 while (iter2.hasNext()) {
395 String id = (String) iter2.next();
396 CommonsValidator v = (CommonsValidator) map.get(id);
397 writer.write("this[" + k + "] = ");
398 k++;
399 writeJavaScriptParams(writer, context, id, v);
400 writer.write(";\n");
401 }
402 writer.write("\t}\n");
403 }
404
405 buff.append(");\n\t\n}");
406
407 }
408 formValidators.clear();
409
410
411 buff.append("\n\treturn bValid;\n")
412 .append("}\n");
413
414 writer.write(buff.toString());
415
416
417
418 List types = new ArrayList(validators.keySet());
419 types.add("includeJavaScriptUtilities");
420
421 Iterator iter = types.iterator();
422 while (iter.hasNext()) {
423 String type = (String) iter.next();
424 ValidatorAction a = CommonsValidator.getValidatorAction(type);
425 writer.write(a.getJavascript());
426 writer.write("\n");
427 }
428
429 types.clear();
430
431 }
432
433
434 /***
435 * <p>Backslash-escapes the following characters from the input string:
436 * ", ', \, \r, \n.</p>
437 *
438 * <p>This method escapes characters that will result in an invalid
439 * Javascript statement within the validator Javascript.</p>
440 *
441 * @param str The string to escape.
442 * @return The string <code>s</code> with each instance of a double quote,
443 * single quote, backslash, carriage-return, or line feed escaped
444 * with a leading backslash.
445 */
446 private String escapeJavascript(String str) {
447 if (str == null) {
448 return null;
449 }
450
451 int length = str.length();
452
453 if (length == 0) {
454 return str;
455 }
456
457
458 StringBuffer out = new StringBuffer(length + 4);
459
460
461 for (int i = 0; i < length; i++) {
462 char c = str.charAt(i);
463
464 if ((c == '"') || (c == '\'') || (c == '//') || (c == '\n')
465 || (c == '\r')) {
466 out.append('//');
467 }
468
469 out.append(c);
470 }
471
472 return out.toString();
473 }
474
475
476
477 /***
478 * <p>Returns an array of validator types organized by dependencies.</p>
479 *
480 * @param typeSet The type set to be processed
481 * @return array of validator types ordered by dependencies
482 */
483 private List getTypesOrderedByDependencies(Set typeSet) {
484
485 List tmpList = new ArrayList(typeSet);
486
487 ordered: for (int i = 0; i < tmpList.size(); i++) {
488 boolean swap = false;
489 for (int j = 0; j < tmpList.size(); j++) {
490 String type = (String) tmpList.get(j);
491 ValidatorAction a = CommonsValidator.getValidatorAction(type);
492
493 List dependencies = a.getDependencyList();
494 if (dependencies != null && dependencies.size() > 0) {
495 int max = -1;
496 for (int n = 0; n < dependencies.size(); n++) {
497 max = Math.max(max, tmpList.indexOf(dependencies.get(n)));
498 }
499 if (max > j) {
500 String tmp = (String) tmpList.get(j);
501 tmpList.remove(j);
502 tmpList.add(max, tmp);
503 swap = true;
504 j = max;
505 }
506 }
507
508 }
509 if (!swap) {
510 break ordered;
511 }
512 }
513
514 return tmpList;
515
516 }
517
518 /***
519 * <p>Writes the JavaScript parameters for the client-side
520 * validation code.</p>
521 *
522 * @param writer A response writer
523 * @param context The FacesContext for this request
524 * @param id The clientId of the owning component
525 * @param v The Commons validator
526 *
527 * @exception IOException if an input/output error occurs
528 */
529 public void writeJavaScriptParams(ResponseWriter writer,
530 FacesContext context, String id, CommonsValidator v) throws IOException {
531
532 Map localVars = null;
533
534
535 if (validatorVars != null && validatorVars.containsKey(id)) {
536 Map typeVars = (Map) validatorVars.get(id);
537 if (typeVars != null && typeVars.containsKey(v.getType())) {
538 localVars = (Map) typeVars.get(v.getType());
539 }
540 }
541
542 Tags tagUtils = new Tags();
543 ValidatorAction validatorAction = v.getValidatorAction();
544 writer.write("new Array(\"");
545 writer.write(id);
546 writer.write("\", \"");
547 writer.write(v.getErrorMessage(context, validatorAction, localVars));
548 writer.write("\", new Function(\"x\", \"return {");
549
550 Iterator vi = v.getVars().entrySet().iterator();
551
552 boolean first = true;
553
554
555
556 Map idVars = (Map) validatorVars.get(id);
557
558 next: while (vi.hasNext()) {
559 Map.Entry e = (Map.Entry) vi.next();
560
561 Object value = e.getValue();
562
563
564 if (idVars != null && idVars.containsKey(v.getType())) {
565
566 Map typeVars = (Map) idVars.get(v.getType());
567
568 if (typeVars != null && typeVars.containsKey(e.getKey())) {
569 value = typeVars.get(e.getKey());
570 }
571 } else {
572 if (value != null && value instanceof String
573 && isValueReference((String) e.getValue())) {
574
575 value = tagUtils.eval((String) e.getValue());
576 }
577 }
578
579 if (value == null) {
580 continue next;
581 }
582 String name = (String) e.getKey();
583 if (!first) {
584 writer.write(",");
585 } else {
586 first = false;
587 }
588 writer.write(name);
589 writer.write(":");
590
591 String jsType = v.getVarType(name);
592
593 if (jsType.equals(Var.JSTYPE_REGEXP)) {
594 writer.write("/");
595 } else {
596 writer.write("'");
597 }
598
599 writer.write(escapeJavascript(value.toString()));
600
601 if (jsType.equals(Var.JSTYPE_REGEXP)) {
602 writer.write("/");
603 } else {
604 writer.write("'");
605 }
606 }
607 writer.write("}[x];\"))");
608 }
609
610
611 /***
612 * <p>Traverses up the tree looking for the owning form.
613 * Returns the parent <code>UIForm</code> or empty string
614 * if one is not found.</p>
615 *
616 * @param context FacesContext for the current request
617 * @param component <code>UIForm</code> parent of the component.
618 * @return client id of the parent form component
619 */
620 public String findForm(FacesContext context, UIComponent component) {
621
622 UIComponent parent = component.getParent();
623 if (parent != null) {
624 if (parent instanceof UIForm) {
625 return parent.getClientId(context).replace(UINamingContainer.SEPARATOR_CHAR, '_');
626 } else {
627 return findForm(context, parent);
628 }
629 }
630
631 return "";
632 }
633
634
635 /***
636 * <p>Begin encoding for this component. This method
637 * finds all Commons validators attached to components
638 * in the current component hierarchy and writes out
639 * JavaScript code to invoke those validators, in turn.</p>
640 *
641 * @param context The FacesContext for this request
642 *
643 * @exception IOException if an input/output error occurs
644 */
645 public void encodeBegin(FacesContext context) throws IOException {
646 ResponseWriter writer = context.getResponseWriter();
647
648 validators.clear();
649 findCommonsValidators(context.getViewRoot(), context);
650
651 writeScriptStart(writer);
652 writeValidationFunctions(writer, context);
653 writeScriptEnd(writer);
654 }
655
656
657 /***
658 * <p>Return true if the specified string contains an EL expression.</p>
659 *
660 * <p>This is taken almost verbatim from {@link javax.faces.webapp.UIComponentTag}
661 * in order to remove JSP dependencies from the renderers.</p>
662 *
663 * @param value String to be checked for being an expression
664 */
665 private boolean isValueReference(String value) {
666
667 if (value == null) {
668 return false;
669 }
670
671 int start = value.indexOf("#{");
672 if (start < 0) {
673 return false;
674 }
675
676 int end = value.lastIndexOf('}');
677 return (end >= 0) && (start < end);
678 }
679
680
681 }