View Javadoc

1   /*
2    * $Id: FormTag.java 405216 2006-05-08 23:06:48Z niallp $
3    *
4    * Copyright 1999-2004 The Apache Software Foundation.
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    *
10   *      http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.struts.taglib.html;
19  
20  import org.apache.struts.Globals;
21  import org.apache.struts.action.ActionForm;
22  import org.apache.struts.action.ActionMapping;
23  import org.apache.struts.action.ActionServlet;
24  import org.apache.struts.config.FormBeanConfig;
25  import org.apache.struts.config.ModuleConfig;
26  import org.apache.struts.taglib.TagUtils;
27  import org.apache.struts.util.MessageResources;
28  import org.apache.struts.util.RequestUtils;
29  
30  import javax.servlet.http.HttpServletRequest;
31  import javax.servlet.http.HttpServletResponse;
32  import javax.servlet.http.HttpSession;
33  import javax.servlet.jsp.JspException;
34  import javax.servlet.jsp.JspWriter;
35  import javax.servlet.jsp.PageContext;
36  import javax.servlet.jsp.tagext.TagSupport;
37  
38  import java.io.IOException;
39  
40  /***
41   * Custom tag that represents an input form, associated with a bean whose
42   * properties correspond to the various fields of the form.
43   *
44   * @version $Rev: 405216 $ $Date: 2006-05-08 16:06:48 -0700 (Mon, 08 May 2006) $
45   */
46  public class FormTag extends TagSupport {
47      /***
48       * The line ending string.
49       */
50      protected static String lineEnd = System.getProperty("line.separator");
51  
52      /***
53       * The message resources for this package.
54       */
55      protected static MessageResources messages =
56          MessageResources.getMessageResources(Constants.Package
57              + ".LocalStrings");
58  
59      // ----------------------------------------------------- Instance Variables
60  
61      /***
62       * The action URL to which this form should be submitted, if any.
63       */
64      protected String action = null;
65  
66      /***
67       * A postback action URL to which this form should be submitted, if any.
68       */
69      private String postbackAction = null;
70  
71      /***
72       * The module configuration for our module.
73       */
74      protected ModuleConfig moduleConfig = null;
75  
76      /***
77       * The content encoding to be used on a POST submit.
78       */
79      protected String enctype = null;
80  
81      /***
82       * The name of the field to receive focus, if any.
83       */
84      protected String focus = null;
85  
86      /***
87       * The index in the focus field array to receive focus.  This only applies
88       * if the field given in the focus attribute is actually an array of
89       * fields.  This allows a specific field in a radio button array to
90       * receive focus while still allowing indexed field names like
91       * "myRadioButtonField[1]" to be passed in the focus attribute.
92       *
93       * @since Struts 1.1
94       */
95      protected String focusIndex = null;
96  
97      /***
98       * The ActionMapping defining where we will be submitting this form
99       */
100     protected ActionMapping mapping = null;
101 
102     /***
103      * The request method used when submitting this form.
104      */
105     protected String method = null;
106 
107     /***
108      * The onReset event script.
109      */
110     protected String onreset = null;
111 
112     /***
113      * The onSubmit event script.
114      */
115     protected String onsubmit = null;
116 
117     /***
118      * Include language attribute in the focus script's <script>
119      * element.  This property is ignored in XHTML mode.
120      *
121      * @since Struts 1.2
122      */
123     protected boolean scriptLanguage = true;
124 
125     /***
126      * The ActionServlet instance we are associated with (so that we can
127      * initialize the <code>servlet</code> property on any form bean that we
128      * create).
129      */
130     protected ActionServlet servlet = null;
131 
132     /***
133      * The style attribute associated with this tag.
134      */
135     protected String style = null;
136 
137     /***
138      * The style class associated with this tag.
139      */
140     protected String styleClass = null;
141 
142     /***
143      * The identifier associated with this tag.
144      */
145     protected String styleId = null;
146 
147     /***
148      * The window target.
149      */
150     protected String target = null;
151 
152     /***
153      * The name of the form bean to (create and) use. This is either the same
154      * as the 'name' attribute, if that was specified, or is obtained from the
155      * associated <code>ActionMapping</code> otherwise.
156      */
157     protected String beanName = null;
158 
159     /***
160      * The scope of the form bean to (create and) use. This is either the same
161      * as the 'scope' attribute, if that was specified, or is obtained from
162      * the associated <code>ActionMapping</code> otherwise.
163      */
164     protected String beanScope = null;
165 
166     /***
167      * The type of the form bean to (create and) use. This is either the same
168      * as the 'type' attribute, if that was specified, or is obtained from the
169      * associated <code>ActionMapping</code> otherwise.
170      */
171     protected String beanType = null;
172 
173     /***
174      * The list of character encodings for input data that the server should
175      * accept.
176      */
177     protected String acceptCharset = null;
178 
179     /***
180      * Controls whether child controls should be 'disabled'.
181      */
182     private boolean disabled = false;
183 
184     /***
185      * Controls whether child controls should be 'readonly'.
186      */
187     protected boolean readonly = false;
188 
189     // ------------------------------------------------------------- Properties
190 
191     /***
192      * Return the name of the form bean corresponding to this tag. There is no
193      * corresponding setter method; this method exists so that the nested tag
194      * classes can obtain the actual bean name derived from other attributes
195      * of the tag.
196      */
197     public String getBeanName() {
198         return beanName;
199     }
200 
201     /***
202      * Return the action URL to which this form should be submitted.
203      */
204     public String getAction() {
205         return (this.action);
206     }
207 
208     /***
209      * Set the action URL to which this form should be submitted.
210      *
211      * @param action The new action URL
212      */
213     public void setAction(String action) {
214         this.action = action;
215     }
216 
217     /***
218      * Return the content encoding used when submitting this form.
219      */
220     public String getEnctype() {
221         return (this.enctype);
222     }
223 
224     /***
225      * Set the content encoding used when submitting this form.
226      *
227      * @param enctype The new content encoding
228      */
229     public void setEnctype(String enctype) {
230         this.enctype = enctype;
231     }
232 
233     /***
234      * Return the focus field name for this form.
235      */
236     public String getFocus() {
237         return (this.focus);
238     }
239 
240     /***
241      * Set the focus field name for this form.
242      *
243      * @param focus The new focus field name
244      */
245     public void setFocus(String focus) {
246         this.focus = focus;
247     }
248 
249     /***
250      * Return the request method used when submitting this form.
251      */
252     public String getMethod() {
253         return (this.method);
254     }
255 
256     /***
257      * Set the request method used when submitting this form.
258      *
259      * @param method The new request method
260      */
261     public void setMethod(String method) {
262         this.method = method;
263     }
264 
265     /***
266      * Return the onReset event script.
267      */
268     public String getOnreset() {
269         return (this.onreset);
270     }
271 
272     /***
273      * Set the onReset event script.
274      *
275      * @param onReset The new event script
276      */
277     public void setOnreset(String onReset) {
278         this.onreset = onReset;
279     }
280 
281     /***
282      * Return the onSubmit event script.
283      */
284     public String getOnsubmit() {
285         return (this.onsubmit);
286     }
287 
288     /***
289      * Set the onSubmit event script.
290      *
291      * @param onSubmit The new event script
292      */
293     public void setOnsubmit(String onSubmit) {
294         this.onsubmit = onSubmit;
295     }
296 
297     /***
298      * Return the style attribute for this tag.
299      */
300     public String getStyle() {
301         return (this.style);
302     }
303 
304     /***
305      * Set the style attribute for this tag.
306      *
307      * @param style The new style attribute
308      */
309     public void setStyle(String style) {
310         this.style = style;
311     }
312 
313     /***
314      * Return the style class for this tag.
315      */
316     public String getStyleClass() {
317         return (this.styleClass);
318     }
319 
320     /***
321      * Set the style class for this tag.
322      *
323      * @param styleClass The new style class
324      */
325     public void setStyleClass(String styleClass) {
326         this.styleClass = styleClass;
327     }
328 
329     /***
330      * Return the style identifier for this tag.
331      */
332     public String getStyleId() {
333         return (this.styleId);
334     }
335 
336     /***
337      * Set the style identifier for this tag.
338      *
339      * @param styleId The new style identifier
340      */
341     public void setStyleId(String styleId) {
342         this.styleId = styleId;
343     }
344 
345     /***
346      * Return the window target.
347      */
348     public String getTarget() {
349         return (this.target);
350     }
351 
352     /***
353      * Set the window target.
354      *
355      * @param target The new window target
356      */
357     public void setTarget(String target) {
358         this.target = target;
359     }
360 
361     /***
362      * Return the list of character encodings accepted.
363      */
364     public String getAcceptCharset() {
365         return acceptCharset;
366     }
367 
368     /***
369      * Set the list of character encodings accepted.
370      *
371      * @param acceptCharset The list of character encodings
372      */
373     public void setAcceptCharset(String acceptCharset) {
374         this.acceptCharset = acceptCharset;
375     }
376 
377     /***
378      * Sets the disabled event handler.
379      */
380     public void setDisabled(boolean disabled) {
381         this.disabled = disabled;
382     }
383 
384     /***
385      * Returns the disabled event handler.
386      */
387     public boolean isDisabled() {
388         return disabled;
389     }
390 
391     /***
392      * Sets the readonly event handler.
393      */
394     public void setReadonly(boolean readonly) {
395         this.readonly = readonly;
396     }
397 
398     /***
399      * Returns the readonly event handler.
400      */
401     public boolean isReadonly() {
402         return readonly;
403     }
404 
405     // --------------------------------------------------------- Public Methods
406 
407     /***
408      * Render the beginning of this form.
409      *
410      * @throws JspException if a JSP exception has occurred
411      */
412     public int doStartTag() throws JspException {
413 
414         postbackAction = null;
415 
416         // Look up the form bean name, scope, and type if necessary
417         this.lookup();
418 
419         // Create an appropriate "form" element based on our parameters
420         StringBuffer results = new StringBuffer();
421 
422         results.append(this.renderFormStartElement());
423 
424         results.append(this.renderToken());
425 
426         TagUtils.getInstance().write(pageContext, results.toString());
427 
428         // Store this tag itself as a page attribute
429         pageContext.setAttribute(Constants.FORM_KEY, this,
430             PageContext.REQUEST_SCOPE);
431 
432         this.initFormBean();
433 
434         return (EVAL_BODY_INCLUDE);
435     }
436 
437     /***
438      * Locate or create the bean associated with our form.
439      *
440      * @throws JspException
441      * @since Struts 1.1
442      */
443     protected void initFormBean()
444         throws JspException {
445         int scope = PageContext.SESSION_SCOPE;
446 
447         if ("request".equalsIgnoreCase(beanScope)) {
448             scope = PageContext.REQUEST_SCOPE;
449         }
450 
451         Object bean = pageContext.getAttribute(beanName, scope);
452 
453         if (bean == null) {
454             // New and improved - use the values from the action mapping
455             bean =
456                 RequestUtils.createActionForm((HttpServletRequest) pageContext
457                     .getRequest(), mapping, moduleConfig, servlet);
458 
459             if (bean instanceof ActionForm) {
460                 ((ActionForm) bean).reset(mapping,
461                     (HttpServletRequest) pageContext.getRequest());
462             }
463 
464             if (bean == null) {
465                 throw new JspException(messages.getMessage("formTag.create",
466                         beanType));
467             }
468 
469             pageContext.setAttribute(beanName, bean, scope);
470         }
471 
472         pageContext.setAttribute(Constants.BEAN_KEY, bean,
473             PageContext.REQUEST_SCOPE);
474     }
475 
476     /***
477      * Generates the opening <code>&lt;form&gt;</code> element with
478      * appropriate attributes.
479      *
480      * @since Struts 1.1
481      */
482     protected String renderFormStartElement()
483         throws JspException {
484         StringBuffer results = new StringBuffer("<form");
485 
486         // render attributes
487         renderName(results);
488 
489         renderAttribute(results, "method",
490             (getMethod() == null) ? "post" : getMethod());
491         renderAction(results);
492         renderAttribute(results, "accept-charset", getAcceptCharset());
493         renderAttribute(results, "class", getStyleClass());
494         renderAttribute(results, "enctype", getEnctype());
495         renderAttribute(results, "onreset", getOnreset());
496         renderAttribute(results, "onsubmit", getOnsubmit());
497         renderAttribute(results, "style", getStyle());
498         renderAttribute(results, "target", getTarget());
499 
500         // Hook for additional attributes
501         renderOtherAttributes(results);
502 
503         results.append(">");
504 
505         return results.toString();
506     }
507 
508     /***
509      * Renders the name of the form.  If XHTML is set to true, the name will
510      * be rendered as an 'id' attribute, otherwise as a 'name' attribute.
511      */
512     protected void renderName(StringBuffer results)
513         throws JspException {
514         if (this.isXhtml()) {
515             if (getStyleId() == null) {
516                 renderAttribute(results, "id", beanName);
517             } else {
518                 throw new JspException(messages.getMessage("formTag.ignoredId"));
519             }
520         } else {
521             renderAttribute(results, "name", beanName);
522             renderAttribute(results, "id", getStyleId());
523         }
524     }
525 
526     /***
527      * Renders the action attribute
528      */
529     protected void renderAction(StringBuffer results) {
530         String calcAction = (this.action == null ? postbackAction : this.action);
531         HttpServletResponse response =
532             (HttpServletResponse) this.pageContext.getResponse();
533 
534         results.append(" action=\"");
535         results.append(response.encodeURL(
536                 TagUtils.getInstance().getActionMappingURL(calcAction,
537                     this.pageContext)));
538 
539         results.append("\"");
540     }
541 
542     /***
543      * 'Hook' to enable this tag to be extended and additional attributes
544      * added.
545      */
546     protected void renderOtherAttributes(StringBuffer results) {
547     }
548 
549     /***
550      * Generates a hidden input field with token information, if any. The
551      * field is added within a div element for HTML 4.01 Strict compliance.
552      *
553      * @return A hidden input field containing the token.
554      * @since Struts 1.1
555      */
556     protected String renderToken() {
557         StringBuffer results = new StringBuffer();
558         HttpSession session = pageContext.getSession();
559 
560         if (session != null) {
561             String token =
562                 (String) session.getAttribute(Globals.TRANSACTION_TOKEN_KEY);
563 
564             if (token != null) {
565                 results.append("<div><input type=\"hidden\" name=\"");
566                 results.append(Constants.TOKEN_KEY);
567                 results.append("\" value=\"");
568                 results.append(token);
569 
570                 if (this.isXhtml()) {
571                     results.append("\" />");
572                 } else {
573                     results.append("\">");
574                 }
575 
576                 results.append("</div>");
577             }
578         }
579 
580         return results.toString();
581     }
582 
583     /***
584      * Renders attribute="value" if not null
585      */
586     protected void renderAttribute(StringBuffer results, String attribute,
587         String value) {
588         if (value != null) {
589             results.append(" ");
590             results.append(attribute);
591             results.append("=\"");
592             results.append(value);
593             results.append("\"");
594         }
595     }
596 
597     /***
598      * Render the end of this form.
599      *
600      * @throws JspException if a JSP exception has occurred
601      */
602     public int doEndTag() throws JspException {
603         // Remove the page scope attributes we created
604         pageContext.removeAttribute(Constants.BEAN_KEY,
605             PageContext.REQUEST_SCOPE);
606         pageContext.removeAttribute(Constants.FORM_KEY,
607             PageContext.REQUEST_SCOPE);
608 
609         // Render a tag representing the end of our current form
610         StringBuffer results = new StringBuffer("</form>");
611 
612         // Render JavaScript to set the input focus if required
613         if (this.focus != null) {
614             results.append(this.renderFocusJavascript());
615         }
616 
617         // Print this value to our output writer
618         JspWriter writer = pageContext.getOut();
619 
620         try {
621             writer.print(results.toString());
622         } catch (IOException e) {
623             throw new JspException(messages.getMessage("common.io", e.toString()));
624         }
625 
626         postbackAction = null;
627 
628         // Continue processing this page
629         return (EVAL_PAGE);
630     }
631 
632     /***
633      * Generates javascript to set the initial focus to the form element given
634      * in the tag's "focus" attribute.
635      *
636      * @since Struts 1.1
637      */
638     protected String renderFocusJavascript() {
639         StringBuffer results = new StringBuffer();
640 
641         results.append(lineEnd);
642         results.append("<script type=\"text/javascript\"");
643 
644         if (!this.isXhtml() && this.scriptLanguage) {
645             results.append(" language=\"JavaScript\"");
646         }
647 
648         results.append(">");
649         results.append(lineEnd);
650 
651         // xhtml script content shouldn't use the browser hiding trick
652         if (!this.isXhtml()) {
653             results.append("  <!--");
654             results.append(lineEnd);
655         }
656 
657         // Construct the control name that will receive focus.
658         // This does not include any index.
659         StringBuffer focusControl = new StringBuffer("document.forms[\"");
660 
661         focusControl.append(beanName);
662         focusControl.append("\"].elements[\"");
663         focusControl.append(this.focus);
664         focusControl.append("\"]");
665 
666         results.append("  var focusControl = ");
667         results.append(focusControl.toString());
668         results.append(";");
669         results.append(lineEnd);
670         results.append(lineEnd);
671 
672         results.append("  if (focusControl.type != \"hidden\" && ");
673         results.append("!focusControl.disabled && ");
674         results.append("focusControl.style.display != \"none\") {");
675         results.append(lineEnd);
676 
677         // Construct the index if needed and insert into focus statement
678         String index = "";
679 
680         if (this.focusIndex != null) {
681             StringBuffer sb = new StringBuffer("[");
682 
683             sb.append(this.focusIndex);
684             sb.append("]");
685             index = sb.toString();
686         }
687 
688         results.append("     focusControl");
689         results.append(index);
690         results.append(".focus();");
691         results.append(lineEnd);
692 
693         results.append("  }");
694         results.append(lineEnd);
695 
696         if (!this.isXhtml()) {
697             results.append("  // -->");
698             results.append(lineEnd);
699         }
700 
701         results.append("</script>");
702         results.append(lineEnd);
703 
704         return results.toString();
705     }
706 
707     /***
708      * Release any acquired resources.
709      */
710     public void release() {
711         super.release();
712         action = null;
713         moduleConfig = null;
714         enctype = null;
715         disabled = false;
716         focus = null;
717         focusIndex = null;
718         mapping = null;
719         method = null;
720         onreset = null;
721         onsubmit = null;
722         readonly = false;
723         servlet = null;
724         style = null;
725         styleClass = null;
726         styleId = null;
727         target = null;
728         acceptCharset = null;
729     }
730 
731     // ------------------------------------------------------ Protected Methods
732 
733     /***
734      * Look up values for the <code>name</code>, <code>scope</code>, and
735      * <code>type</code> properties if necessary.
736      *
737      * @throws JspException if a required value cannot be looked up
738      */
739     protected void lookup() throws JspException {
740 
741         // Look up the module configuration information we need
742         moduleConfig = TagUtils.getInstance().getModuleConfig(pageContext);
743 
744         if (moduleConfig == null) {
745             JspException e =
746                 new JspException(messages.getMessage("formTag.collections"));
747 
748             pageContext.setAttribute(Globals.EXCEPTION_KEY, e,
749                 PageContext.REQUEST_SCOPE);
750             throw e;
751         }
752 
753         String calcAction = this.action;
754 
755         // If the action is not specified, use the original request uri
756         if (this.action == null) {
757             HttpServletRequest request =
758                 (HttpServletRequest) pageContext.getRequest();
759             postbackAction =
760                 (String) request.getAttribute(Globals.ORIGINAL_URI_KEY);
761 
762             String prefix = moduleConfig.getPrefix();
763             if (postbackAction != null && prefix.length() > 0 && postbackAction.startsWith(prefix)) {
764                 postbackAction = postbackAction.substring(prefix.length());
765             }
766             calcAction = postbackAction;
767         }
768 
769         servlet =
770             (ActionServlet) pageContext.getServletContext().getAttribute(Globals.ACTION_SERVLET_KEY);
771 
772         // Look up the action mapping we will be submitting to
773         String mappingName =
774             TagUtils.getInstance().getActionMappingName(calcAction);
775 
776         mapping = (ActionMapping) moduleConfig.findActionConfig(mappingName);
777 
778         if (mapping == null) {
779             JspException e =
780                 new JspException(messages.getMessage("formTag.mapping",
781                         mappingName));
782 
783             pageContext.setAttribute(Globals.EXCEPTION_KEY, e,
784                 PageContext.REQUEST_SCOPE);
785             throw e;
786         }
787 
788         // Look up the form bean definition
789         FormBeanConfig formBeanConfig =
790             moduleConfig.findFormBeanConfig(mapping.getName());
791 
792         if (formBeanConfig == null) {
793             JspException e = null;
794 
795             if (mapping.getName() == null) {
796                 e = new JspException(messages.getMessage("formTag.name", calcAction));
797             } else {
798                 e = new JspException(messages.getMessage("formTag.formBean",
799                             mapping.getName(), calcAction));
800             }
801 
802             pageContext.setAttribute(Globals.EXCEPTION_KEY, e,
803                 PageContext.REQUEST_SCOPE);
804             throw e;
805         }
806 
807         // Calculate the required values
808         beanName = mapping.getAttribute();
809         beanScope = mapping.getScope();
810         beanType = formBeanConfig.getType();
811     }
812 
813     /***
814      * Returns true if this tag should render as xhtml.
815      */
816     private boolean isXhtml() {
817         return TagUtils.getInstance().isXhtml(this.pageContext);
818     }
819 
820     /***
821      * Returns the focusIndex.
822      *
823      * @return String
824      */
825     public String getFocusIndex() {
826         return focusIndex;
827     }
828 
829     /***
830      * Sets the focusIndex.
831      *
832      * @param focusIndex The focusIndex to set
833      */
834     public void setFocusIndex(String focusIndex) {
835         this.focusIndex = focusIndex;
836     }
837 
838     /***
839      * Gets whether or not the focus script's &lt;script&gt; element will
840      * include the language attribute.
841      *
842      * @return true if language attribute will be included.
843      * @since Struts 1.2
844      */
845     public boolean getScriptLanguage() {
846         return this.scriptLanguage;
847     }
848 
849     /***
850      * Sets whether or not the focus script's &lt;script&gt; element will
851      * include the language attribute.
852      *
853      * @since Struts 1.2
854      */
855     public void setScriptLanguage(boolean scriptLanguage) {
856         this.scriptLanguage = scriptLanguage;
857     }
858 }