1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.struts.faces.taglib;
18
19 import java.io.IOException;
20 import java.util.ArrayList;
21 import java.util.Collections;
22 import java.util.Comparator;
23 import java.util.Iterator;
24 import java.util.List;
25 import java.util.Locale;
26 import java.util.Map;
27
28 import javax.faces.component.UIComponent;
29 import javax.faces.context.FacesContext;
30 import javax.faces.webapp.UIComponentTag;
31 import javax.servlet.http.HttpServletRequest;
32 import javax.servlet.jsp.JspException;
33 import javax.servlet.jsp.JspWriter;
34 import javax.servlet.jsp.PageContext;
35 import javax.servlet.jsp.tagext.BodyTagSupport;
36 import javax.servlet.jsp.tagext.Tag;
37 import javax.servlet.ServletContext;
38
39 import org.apache.commons.validator.Field;
40 import org.apache.commons.validator.Form;
41 import org.apache.commons.validator.ValidatorAction;
42 import org.apache.commons.validator.ValidatorResources;
43 import org.apache.commons.validator.Var;
44 import org.apache.commons.validator.util.ValidatorUtils;
45 import org.apache.struts.Globals;
46 import org.apache.struts.config.ModuleConfig;
47 import org.apache.struts.faces.component.FormComponent;
48 import org.apache.struts.taglib.TagUtils;
49 import org.apache.struts.util.MessageResources;
50 import org.apache.struts.util.ModuleUtils;
51 import org.apache.struts.validator.Resources;
52 import org.apache.struts.validator.ValidatorPlugIn;
53
54 /***
55 * Custom tag that generates JavaScript for client side validation based
56 * on the validation rules loaded by the <code>ValidatorPlugIn</code>
57 * defined in the struts-config.xml file. This is based on the code in
58 * the corresponding class of the Struts HTML tag library, modified as needed
59 * to reflect differences in the way JavaServer Faces renders field
60 * identifiers.
61 *
62 * @version $Rev: 421138 $ $Date: 2006-07-11 22:41:40 -0700 (Tue, 11 Jul 2006) $
63 */
64 public class JavascriptValidatorTag extends BodyTagSupport {
65
66
67
68 /***
69 * The servlet context attribute key for our resources.
70 */
71 protected String bundle = Globals.MESSAGES_KEY;
72
73 /***
74 * The default locale on our server.
75 * @deprecated This variable is no longer used.
76 */
77 protected static Locale defaultLocale = Locale.getDefault();
78
79 /***
80 * The name of the form that corresponds with the action name
81 * in struts-config.xml. Specifying a form name places a
82 * <script> </script> around the javascript.
83 */
84 protected String formName = null;
85
86 /***
87 * The line ending string.
88 */
89 protected static String lineEnd = System.getProperty("line.separator");
90
91 /***
92 * The current page number of a multi-part form.
93 * Only valid when the formName attribute is set.
94 */
95 protected int page = 0;
96
97 /***
98 * This will be used as is for the JavaScript validation method name if it
99 * has a value. This is the method name of the main JavaScript method that
100 * the form calls to perform validations.
101 */
102 protected String methodName = null;
103
104 /***
105 * The static JavaScript methods will only be printed if this is set to
106 * "true".
107 */
108 protected String staticJavascript = "true";
109
110 /***
111 * The dynamic JavaScript objects will only be generated if this is set to
112 * "true".
113 */
114 protected String dynamicJavascript = "true";
115
116 /***
117 * The src attribute for html script element (used to include an external
118 * script resource). The src attribute is only recognized
119 * when the formName attribute is specified.
120 */
121 protected String src = null;
122
123 /***
124 * The JavaScript methods will enclosed with html comments if this is set to
125 * "true".
126 */
127 protected String htmlComment = "true";
128
129 /***
130 * Hide JavaScript methods in a CDATA section for XHTML when "true".
131 */
132 protected String cdata = "true";
133
134 private String htmlBeginComment = "\n<!-- Begin \n";
135
136 private String htmlEndComment = "//End --> \n";
137
138 /***
139 * Gets the key (form name) that will be used
140 * to retrieve a set of validation rules to be
141 * performed on the bean passed in for validation.
142 */
143 public String getFormName() {
144 return formName;
145 }
146
147 /***
148 * Sets the key (form name) that will be used
149 * to retrieve a set of validation rules to be
150 * performed on the bean passed in for validation.
151 * Specifying a form name places a
152 * <script> </script> tag around the javascript.
153 */
154 public void setFormName(String formName) {
155 this.formName = formName;
156 }
157
158 /***
159 * Gets the current page number of a multi-part form.
160 * Only field validations with a matching page numer
161 * will be generated that match the current page number.
162 * Only valid when the formName attribute is set.
163 */
164 public int getPage() {
165 return page;
166 }
167
168 /***
169 * Sets the current page number of a multi-part form.
170 * Only field validations with a matching page numer
171 * will be generated that match the current page number.
172 * Only valid when the formName attribute is set.
173 */
174 public void setPage(int page) {
175 this.page = page;
176 }
177
178 /***
179 * Gets the method name that will be used for the Javascript
180 * validation method name if it has a value. This overrides
181 * the auto-generated method name based on the key (form name)
182 * passed in.
183 */
184 public String getMethod() {
185 return methodName;
186 }
187
188 /***
189 * Sets the method name that will be used for the Javascript
190 * validation method name if it has a value. This overrides
191 * the auto-generated method name based on the key (form name)
192 * passed in.
193 */
194 public void setMethod(String methodName) {
195 this.methodName = methodName;
196 }
197
198 /***
199 * Gets whether or not to generate the static
200 * JavaScript. If this is set to 'true', which
201 * is the default, the static JavaScript will be generated.
202 */
203 public String getStaticJavascript() {
204 return staticJavascript;
205 }
206
207 /***
208 * Sets whether or not to generate the static
209 * JavaScript. If this is set to 'true', which
210 * is the default, the static JavaScript will be generated.
211 */
212 public void setStaticJavascript(String staticJavascript) {
213 this.staticJavascript = staticJavascript;
214 }
215
216 /***
217 * Gets whether or not to generate the dynamic
218 * JavaScript. If this is set to 'true', which
219 * is the default, the dynamic JavaScript will be generated.
220 */
221 public String getDynamicJavascript() {
222 return dynamicJavascript;
223 }
224
225 /***
226 * Sets whether or not to generate the dynamic
227 * JavaScript. If this is set to 'true', which
228 * is the default, the dynamic JavaScript will be generated.
229 */
230 public void setDynamicJavascript(String dynamicJavascript) {
231 this.dynamicJavascript = dynamicJavascript;
232 }
233
234 /***
235 * Gets whether or not to delimit the
236 * JavaScript with html comments. If this is set to 'true', which
237 * is the default, the htmlComment will be surround the JavaScript.
238 */
239 public String getHtmlComment() {
240 return htmlComment;
241 }
242
243 /***
244 * Sets whether or not to delimit the
245 * JavaScript with html comments. If this is set to 'true', which
246 * is the default, the htmlComment will be surround the JavaScript.
247 */
248 public void setHtmlComment(String htmlComment) {
249 this.htmlComment = htmlComment;
250 }
251
252 /***
253 * Gets the src attribute's value when defining
254 * the html script element.
255 */
256 public String getSrc() {
257 return src;
258 }
259
260 /***
261 * Sets the src attribute's value when defining
262 * the html script element. The src attribute is only recognized
263 * when the formName attribute is specified.
264 */
265 public void setSrc(String src) {
266 this.src = src;
267 }
268
269 /***
270 * Render the JavaScript for to perform validations based on the form name.
271 *
272 * @exception JspException if a JSP exception has occurred
273 */
274 public int doStartTag() throws JspException {
275 StringBuffer results = new StringBuffer();
276
277 HttpServletRequest request =
278 (HttpServletRequest)pageContext.getRequest();
279 ServletContext servletContext = pageContext.getServletContext();
280 ModuleConfig config =
281 ModuleUtils.getInstance().getModuleConfig(request, servletContext);
282
283 ValidatorResources resources =
284 (ValidatorResources) pageContext.getAttribute(
285 ValidatorPlugIn.VALIDATOR_KEY + config.getPrefix(),
286 PageContext.APPLICATION_SCOPE);
287
288 Locale locale = TagUtils.getInstance().getUserLocale(pageContext, null);
289
290 Form form = resources.getForm(locale, formName);
291 if (form != null) {
292 if ("true".equalsIgnoreCase(dynamicJavascript)) {
293 MessageResources messages =
294 (MessageResources) pageContext.getAttribute(
295 bundle + config.getPrefix(),
296 PageContext.APPLICATION_SCOPE);
297
298 List lActions = new ArrayList();
299 List lActionMethods = new ArrayList();
300
301
302 for (Iterator i = form.getFields().iterator(); i.hasNext();) {
303 Field field = (Field) i.next();
304
305 for (Iterator x = field.getDependencyList().iterator();
306 x.hasNext();) {
307 Object o = x.next();
308
309 if (o != null && !lActionMethods.contains(o)) {
310 lActionMethods.add(o);
311 }
312 }
313
314 }
315
316
317 for (Iterator i = lActionMethods.iterator(); i.hasNext();) {
318 String depends = (String) i.next();
319 ValidatorAction va = resources.getValidatorAction(depends);
320
321
322 if (va == null) {
323 throw new NullPointerException(
324 "Depends string \""
325 + depends
326 + "\" was not found in validator-rules.xml.");
327 }
328
329 String javascript = va.getJavascript();
330 if (javascript != null && javascript.length() > 0) {
331 lActions.add(va);
332 } else {
333 i.remove();
334 }
335 }
336
337 Collections.sort(lActions, new Comparator() {
338 public int compare(Object o1, Object o2) {
339 ValidatorAction va1 = (ValidatorAction) o1;
340 ValidatorAction va2 = (ValidatorAction) o2;
341
342 if ((va1.getDepends() == null
343 || va1.getDepends().length() == 0)
344 && (va2.getDepends() == null
345 || va2.getDepends().length() == 0)) {
346 return 0;
347 } else if (
348 (va1.getDepends() != null
349 && va1.getDepends().length() > 0)
350 && (va2.getDepends() == null
351 || va2.getDepends().length() == 0)) {
352 return 1;
353 } else if (
354 (va1.getDepends() == null
355 || va1.getDepends().length() == 0)
356 && (va2.getDepends() != null
357 && va2.getDepends().length() > 0)) {
358 return -1;
359 } else {
360 return va1.getDependencyList().size() -
361 va2.getDependencyList().size();
362 }
363 }
364 });
365
366 String methods = null;
367 for (Iterator i = lActions.iterator(); i.hasNext();) {
368 ValidatorAction va = (ValidatorAction) i.next();
369
370 if (methods == null) {
371 methods = va.getMethod() + "(form)";
372 } else {
373 methods += " && " + va.getMethod() + "(form)";
374 }
375 }
376
377 results.append(getJavascriptBegin(methods));
378
379 for (Iterator i = lActions.iterator(); i.hasNext();) {
380 ValidatorAction va = (ValidatorAction) i.next();
381 String jscriptVar = null;
382 String functionName = null;
383
384 if (va.getJsFunctionName() != null
385 && va.getJsFunctionName().length() > 0) {
386 functionName = va.getJsFunctionName();
387 } else {
388 functionName = va.getName();
389 }
390
391 if (isStruts11()) {
392 results.append(" function " +
393 functionName + " () { \n");
394 } else {
395 results.append(" function " +
396 formName + "_" + functionName +
397 " () { \n");
398 }
399 for (Iterator x = form.getFields().iterator();
400 x.hasNext();) {
401 Field field = (Field) x.next();
402
403
404
405
406 if (field.isIndexed()
407 || field.getPage() != page
408 || !field.isDependency(va.getName())) {
409
410 continue;
411 }
412
413 String message =
414 Resources.getMessage(messages, locale, va, field);
415
416 message = (message != null) ? message : "";
417
418 jscriptVar = this.getNextVar(jscriptVar);
419
420 results.append(
421 " this."
422 + jscriptVar
423 + " = new Array(\""
424 + getFormClientId()
425 + ":"
426 + field.getKey()
427 + "\", \""
428 + message
429 + "\", ");
430
431 results.append("new Function (\"varName\", \"");
432
433 Map vars = field.getVars();
434
435 Iterator varsIterator = vars.keySet().iterator();
436 while (varsIterator.hasNext()) {
437 String varName = (String) varsIterator.next();
438 Var var = (Var) vars.get(varName);
439 String varValue = var.getValue();
440 String jsType = var.getJsType();
441
442
443
444 if (varName.startsWith("field")) {
445 continue;
446 }
447
448 if (Var.JSTYPE_INT.equalsIgnoreCase(jsType)) {
449 results.append(
450 "this."
451 + varName
452 + "="
453 + ValidatorUtils.replace(
454 varValue,
455 "//",
456 "////")
457 + "; ");
458 } else if (Var.JSTYPE_REGEXP.equalsIgnoreCase(
459 jsType)) {
460 results.append(
461 "this."
462 + varName
463 + "=/"
464 + ValidatorUtils.replace(
465 varValue,
466 "//",
467 "////")
468 + "/; ");
469 } else if (Var.JSTYPE_STRING.equalsIgnoreCase(
470 jsType)) {
471 results.append(
472 "this."
473 + varName
474 + "='"
475 + ValidatorUtils.replace(
476 varValue,
477 "//",
478 "////")
479 + "'; ");
480
481
482 } else if ("mask".equalsIgnoreCase(varName)) {
483 results.append(
484 "this."
485 + varName
486 + "=/"
487 + ValidatorUtils.replace(
488 varValue,
489 "//",
490 "////")
491 + "/; ");
492 } else {
493 results.append(
494 "this."
495 + varName
496 + "='"
497 + ValidatorUtils.replace(
498 varValue,
499 "//",
500 "////")
501 + "'; ");
502 }
503 }
504
505 results.append(" return this[varName];\"));\n");
506 }
507 results.append(" } \n\n");
508 }
509 } else if ("true".equalsIgnoreCase(staticJavascript)) {
510 results.append(this.getStartElement());
511 if ("true".equalsIgnoreCase(htmlComment)) {
512 results.append(htmlBeginComment);
513 }
514 }
515 }
516
517 if ("true".equalsIgnoreCase(staticJavascript)) {
518 results.append(getJavascriptStaticMethods(resources));
519 }
520
521 if (form != null
522 && ("true".equalsIgnoreCase(dynamicJavascript)
523 || "true".equalsIgnoreCase(staticJavascript))) {
524
525 results.append(getJavascriptEnd());
526 }
527
528
529 JspWriter writer = pageContext.getOut();
530 try {
531 writer.print(results.toString());
532 } catch (IOException e) {
533 throw new JspException(e.getMessage());
534 }
535
536 return (EVAL_BODY_TAG);
537
538 }
539
540 /***
541 * Release any acquired resources.
542 */
543 public void release() {
544 super.release();
545 bundle = Globals.MESSAGES_KEY;
546 formName = null;
547 page = 0;
548 methodName = null;
549 staticJavascript = "true";
550 dynamicJavascript = "true";
551 htmlComment = "true";
552 cdata = "true";
553 src = null;
554 formClientId = null;
555 }
556
557 /***
558 * Returns the opening script element and some initial javascript.
559 */
560 protected String getJavascriptBegin(String methods) {
561 StringBuffer sb = new StringBuffer();
562 String name =
563 formName.substring(0, 1).toUpperCase()
564 + formName.substring(1, formName.length());
565
566 sb.append(this.getStartElement());
567
568 if (this.isXhtml() && "true".equalsIgnoreCase(this.cdata)) {
569 sb.append("<![CDATA[\r\n");
570 }
571
572 if (!this.isXhtml() && "true".equals(htmlComment)) {
573 sb.append(htmlBeginComment);
574 }
575 sb.append("\n var bCancel = false; \n\n");
576
577 if (methodName == null || methodName.length() == 0) {
578 sb.append(
579 " function validate"
580 + name
581 + "(form) { "
582 + " \n");
583 } else {
584 sb.append(
585 " function "
586 + methodName
587 + "(form) { "
588 + " \n");
589 }
590 sb.append(" if (bCancel) \n");
591 sb.append(" return true; \n");
592 sb.append(" else \n");
593
594
595 if (methods == null || methods.length() == 0) {
596 sb.append(" return true; \n");
597 } else {
598 sb.append(" return " + methods + "; \n");
599 }
600
601 sb.append(" } \n\n");
602
603 return sb.toString();
604 }
605
606 protected String getJavascriptStaticMethods(ValidatorResources resources) {
607 StringBuffer sb = new StringBuffer();
608
609 sb.append("\n\n");
610
611 Iterator actions = resources.getValidatorActions().values().iterator();
612 while (actions.hasNext()) {
613 ValidatorAction va = (ValidatorAction) actions.next();
614 if (va != null) {
615 String javascript = va.getJavascript();
616 if (javascript != null && javascript.length() > 0) {
617 sb.append(javascript + "\n");
618 }
619 }
620 }
621
622 return sb.toString();
623 }
624
625 /***
626 * Returns the closing script element.
627 */
628 protected String getJavascriptEnd() {
629 StringBuffer sb = new StringBuffer();
630
631 sb.append("\n");
632 if (!this.isXhtml() && "true".equals(htmlComment)){
633 sb.append(htmlEndComment);
634 }
635
636 if (this.isXhtml() && "true".equalsIgnoreCase(this.cdata)) {
637 sb.append("]]>\r\n");
638 }
639
640 sb.append("</script>\n\n");
641
642 return sb.toString();
643 }
644
645 /***
646 * The value <code>null</code> will be returned at the end of the sequence.
647 * ex: "zz" will return <code>null</code>
648 */
649 private String getNextVar(String input) {
650 if (input == null) {
651 return "aa";
652 }
653
654 input = input.toLowerCase();
655
656 for (int i = input.length(); i > 0; i--) {
657 int pos = i - 1;
658
659 char c = input.charAt(pos);
660 c++;
661
662 if (c <= 'z') {
663 if (i == 0) {
664 return c + input.substring(pos, input.length());
665 } else if (i == input.length()) {
666 return input.substring(0, pos) + c;
667 } else {
668 return input.substring(0, pos) + c + input.substring(pos,
669 input.length() - 1);
670 }
671 } else {
672 input = replaceChar(input, pos, 'a');
673 }
674
675 }
676
677 return null;
678
679 }
680
681 /***
682 * Replaces a single character in a <code>String</code>
683 */
684 private String replaceChar(String input, int pos, char c) {
685 if (pos == 0) {
686 return c + input.substring(pos, input.length());
687 } else if (pos == input.length()) {
688 return input.substring(0, pos) + c;
689 } else {
690 return input.substring(0, pos) + c + input.substring(pos,
691 input.length() - 1);
692 }
693 }
694
695 /***
696 * Constructs the beginning <script> element depending on
697 * xhtml status.
698 */
699 private String getStartElement() {
700 StringBuffer start =
701 new StringBuffer("<script type=\"text/javascript\"");
702
703
704 if (!this.isXhtml()) {
705 start.append(" language=\"Javascript1.1\"");
706 }
707
708 if (this.src != null) {
709 start.append(" src=\"" + src + "\"");
710 }
711
712 start.append("> \n");
713 return start.toString();
714 }
715
716 /***
717 * Returns true if this is an xhtml page.
718 */
719 private boolean isXhtml() {
720 return TagUtils.getInstance().isXhtml(this.pageContext);
721 }
722
723 /***
724 * Returns the cdata setting "true" or "false".
725 * @return String - "true" if JavaScript will be hidden in a CDATA section
726 */
727 public String getCdata() {
728 return cdata;
729 }
730
731 /***
732 * Sets the cdata status.
733 * @param cdata The cdata to set
734 */
735 public void setCdata(String cdata) {
736 this.cdata = cdata;
737 }
738
739
740 private String formClientId = null;
741
742 /***
743 * <p>Return the <code>clientId</code> of the form component for which
744 * we are rendering validation Javascript.</p>
745 *
746 * @exception IllegalStateException if we are not nested inside a
747 * UIComponentTag with a child FormComponent matching our form name
748 */
749 private String getFormClientId(){
750
751
752 if (formClientId != null) {
753 return (formClientId);
754 }
755
756
757 Tag parent = getParent();
758 while (parent != null) {
759 if (parent instanceof UIComponentTag) {
760 break;
761 }
762 parent = parent.getParent();
763 }
764 if (parent == null) {
765 throw new IllegalArgumentException
766 ("Not nested inside a UIComponentTag");
767 }
768
769
770 UIComponent parentComponent =
771 ((UIComponentTag) parent).getComponentInstance();
772 if (parentComponent instanceof FormComponent) {
773 if (formName.equals(
774 parentComponent.getAttributes().get("beanName"))) {
775 formClientId = parentComponent.getClientId
776 (FacesContext.getCurrentInstance());
777 return (formClientId);
778 }
779 }
780
781
782 Iterator kids = ((UIComponentTag) parent).
783 getComponentInstance().getChildren().iterator();
784 while (kids.hasNext()) {
785 UIComponent kid = (UIComponent) kids.next();
786 if (!(kid instanceof FormComponent)) {
787 continue;
788 }
789 if (formName.equals(kid.getAttributes().get("beanName"))) {
790 formClientId =
791 kid.getClientId(FacesContext.getCurrentInstance());
792 return (formClientId);
793 }
794 }
795 throw new IllegalArgumentException
796 ("Cannot find child FormComponent for form '" + formName + "'");
797
798 }
799
800
801
802
803
804 private static boolean struts11;
805
806 static {
807 try {
808 JavascriptValidatorTag.class.getClassLoader().loadClass
809 ("org.apache.struts.taglib.TagUtils");
810 struts11 = false;
811 } catch (Exception e) {
812 struts11 = true;
813 }
814 }
815
816
817 /***
818 * <p>Return <code>true</code> if we are running on top of Struts 1.1</p>
819 */
820 private static boolean isStruts11() {
821 return struts11;
822 }
823
824
825 }