View Javadoc

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