View Javadoc
1   /*
2    * $Id$
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  
22  package org.apache.struts.faces.renderer;
23  
24  
25  import java.io.IOException;
26  import java.util.Map;
27  
28  import org.apache.struts.Globals;
29  import org.apache.struts.config.ActionConfig;
30  import org.apache.struts.config.ModuleConfig;
31  import org.apache.struts.faces.component.FormComponent;
32  import org.apache.struts.faces.util.StrutsContext;
33  import org.apache.struts.faces.util.Utils;
34  import org.slf4j.Logger;
35  import org.slf4j.LoggerFactory;
36  
37  import jakarta.faces.component.UIComponent;
38  import jakarta.faces.component.UINamingContainer;
39  import jakarta.faces.context.FacesContext;
40  import jakarta.faces.context.ResponseWriter;
41  import jakarta.servlet.http.HttpSession;
42  
43  
44  /**
45   * <p><code>Renderer</code> implementation for the <code>form</code> tag
46   * from the <em>Struts-Faces Integration Library</em>.</p>
47   *
48   * @version $Rev$ $Date$
49   */
50  
51  public class FormRenderer extends AbstractRenderer {
52  
53  
54      // -------------------------------------------------------- Static Variables
55  
56  
57      /**
58       * The {@code Log} instance for this class.
59       */
60      private final Logger log =
61          LoggerFactory.getLogger(FormRenderer.class);
62  
63  
64      // ---------------------------------------------------------- Public Methods
65  
66  
67      /**
68       * <p>Perform setup processing that will be required for decoding the
69       * incoming request.</p>
70       *
71       * @param context FacesContext for the request we are processing
72       * @param component UIComponent to be processed
73       *
74       * @exception NullPointerException if <code>context</code>
75       *  or <code>component</code> is null
76       */
77      public void decode(FacesContext context, UIComponent component) {
78  
79          if ((context == null) || (component == null)) {
80              throw new NullPointerException();
81          }
82          String clientId = component.getClientId(context);
83          Map<String, String> map = context.getExternalContext().getRequestParameterMap();
84          log.atDebug()
85              .setMessage("decode({}) --> {}")
86              .addArgument(clientId)
87              .addArgument(() -> map.containsKey(clientId))
88              .log();
89          component.getAttributes().put
90              ("submitted",
91               map.containsKey(clientId) ? Boolean.TRUE : Boolean.FALSE);
92  
93      }
94  
95  
96      private static String passThrough[] =
97      { "enctype", "method", "onreset", "onsubmit", "style", "target", };
98  
99  
100     /**
101      * <p>Render the beginning of an HTML <code>&lt;form&gt;</code>
102      * control.</p>
103      *
104      * @param context FacesContext for the request we are processing
105      * @param component UIComponent to be rendered
106      *
107      * @exception IOException if an input/output error occurs while rendering
108      * @exception NullPointerException if <code>context</code>
109      *  or <code>component</code> is null
110      */
111     public void encodeBegin(FacesContext context, UIComponent component)
112         throws IOException {
113 
114         if ((context == null) || (component == null)) {
115             throw new NullPointerException();
116         }
117 
118         // Calculate and cache the form name
119         FormComponent form = (FormComponent) component;
120         String action = form.getAction();
121         ModuleConfig moduleConfig = StrutsContext.getModuleConfig(context);
122         ActionConfig actionConfig = moduleConfig.findActionConfig(action);
123         if (actionConfig == null) {
124             throw new IllegalArgumentException("Cannot find action '" +
125                                                action + "' configuration");
126         }
127         String beanName = actionConfig.getAttribute();
128         if (beanName != null) {
129             form.getAttributes().put("beanName", beanName);
130         }
131 
132         // Look up attribute values we need
133         String clientId = component.getClientId(context);
134         log.debug("encodeBegin({})", clientId);
135         String styleClass = Utils.getMapValue(String.class,
136                 component.getAttributes(), "styleClass");
137 
138         // Render the beginning of this form
139         ResponseWriter writer = context.getResponseWriter();
140         writer.startElement("form", form);
141         writer.writeAttribute("id", clientId, "clientId");
142         if (beanName != null) {
143             writer.writeAttribute("name", beanName, null);
144         }
145         writer.writeAttribute("action", action(context, component), "action");
146         if (styleClass != null) {
147             writer.writeAttribute("class", styleClass, "styleClass");
148         }
149         if (component.getAttributes().get("method") == null) {
150             writer.writeAttribute("method", "post", null);
151         }
152         renderPassThrough(context, component, writer, passThrough);
153         writer.writeText("\n", null);
154 
155         // Add a marker used by our decode() method to note this form is submitted
156         writer.startElement("input", form);
157         writer.writeAttribute("type", "hidden", null);
158         writer.writeAttribute("name", clientId, null);
159         writer.writeAttribute("value", clientId, null);
160         writer.endElement("input");
161         writer.writeText("\n", null);
162 
163         // Add a transaction token if necessary
164         HttpSession session = (HttpSession)
165             context.getExternalContext().getSession(false);
166         if (session != null) {
167             String token = (String)
168                 session.getAttribute(Globals.TRANSACTION_TOKEN_KEY);
169             if (token != null) {
170                 writer.startElement("input", form);
171                 writer.writeAttribute("type", "hidden", null);
172                 writer.writeAttribute
173                     ("name", "org.apache.struts.taglib.html.TOKEN", null);
174                 writer.writeAttribute("value", token, null);
175                 writer.endElement("input");
176                 writer.writeText("\n", null);
177             }
178         }
179 
180         // Create an instance of the form bean if necessary
181         if (component instanceof FormComponent) {
182             ((FormComponent) component).createActionForm(context);
183         }
184 
185     }
186 
187 
188     /**
189      * <p>Render the ending of an HTML <code>&lt;form&gt;</code>
190      * control.</p>
191      *
192      * @param context FacesContext for the request we are processing
193      * @param component UIComponent to be rendered
194      *
195      * @exception IOException if an input/output error occurs while rendering
196      * @exception NullPointerException if <code>context</code>
197      *  or <code>component</code> is null
198      */
199     public void encodeEnd(FacesContext context, UIComponent component)
200         throws IOException {
201 
202         if ((context == null) || (component == null)) {
203             throw new NullPointerException();
204         }
205         String clientId = component.getClientId(context);
206         log.debug("encodeEnd({})", clientId);
207         ResponseWriter writer = context.getResponseWriter();
208 
209         // Render the hidden variable our decode() method uses to detect submits
210         writer.startElement("input", component);
211         writer.writeAttribute("type", "hidden", null);
212         writer.writeAttribute("name", component.getClientId(context), null);
213         writer.writeAttribute("value", component.getClientId(context), null);
214         writer.endElement("input");
215         writer.write("\n");
216 
217         // Write our state information (if necessary)
218         context.getApplication().getViewHandler().writeState(context);
219 
220         // Render the ending of this form
221         writer.endElement("form");
222         writer.writeText("\n", null);
223 
224         // Render focus JavaScript if requested
225         if (!(component instanceof FormComponent)) {
226             return;
227         }
228         String focus = (String) component.getAttributes().get("focus");
229         if (focus == null) {
230             return;
231         }
232         String focusIndex =
233             (String) component.getAttributes().get("focusIndex");
234         writer.writeText("\n", null);
235         FormComponent form = (FormComponent) component;
236         writer.startElement("script", form);
237         writer.writeAttribute("type", "text/javascript", null);
238         if (!isXhtml(component)) {
239             writer.writeAttribute("language", "JavaScript", null);
240         }
241         writer.writeText("\n", null);
242 //        if (!isXhtml(component)) {
243 //            writer.write("<!--\n");
244 //        }
245 
246         StringBuilder sb = new StringBuilder("document.forms[\"");
247         sb.append(clientId);
248         sb.append("\"].elements[\"");
249         sb.append(component.getClientId(context));
250         sb.append(UINamingContainer.getSeparatorChar(context));
251         sb.append(focus);
252         sb.append("\"]");
253         String focusControl = sb.toString();
254 
255         writer.write("  var focusControl = ");
256         writer.write(focusControl);
257         writer.write(";\n");
258         writer.write("  if (focusControl.type != \"hidden\") {\n");
259         writer.write("    ");
260         writer.write(focusControl);
261         if (focusIndex != null) {
262             writer.write("[");
263             writer.write(focusIndex);
264             writer.write("]");
265         }
266         writer.write(".focus();\n");
267         writer.write("  }\n");
268 
269 //        if (!isXhtml(component)) {
270 //            writer.write("// -->\n");
271 //        }
272         writer.endElement("script");
273         writer.writeText("\n", null);
274 
275     }
276 
277 
278     // ------------------------------------------------------- Protected Methods
279 
280 
281     /**
282      * <p>Calculate and return the value to be specified for the
283      * <code>action</action> attribute on the <code>&lt;form&gt;</code>
284      * element to be rendered.</p>
285      *
286      * @param context FacesContext for the current request
287      * @param component Component being processed
288      */
289     protected String action(FacesContext context, UIComponent component) {
290 
291         String actionURL =
292             context.getApplication().getViewHandler().
293             getActionURL(context, context.getViewRoot().getViewId());
294         log.trace("getActionURL({}) --> {}", context.getViewRoot().getViewId(), actionURL);
295         return (context.getExternalContext().encodeActionURL(actionURL));
296 
297     }
298 }