View Javadoc

1   /*
2    * $Id: Form.java 497654 2007-01-19 00:21:57Z rgielen $
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.struts2.components;
22  
23  import java.util.ArrayList;
24  import java.util.Collections;
25  import java.util.Iterator;
26  import java.util.List;
27  import java.util.Set;
28  
29  import javax.servlet.http.HttpServletRequest;
30  import javax.servlet.http.HttpServletResponse;
31  
32  import org.apache.struts2.views.annotations.StrutsTag;
33  import org.apache.struts2.views.annotations.StrutsTagAttribute;
34  import org.apache.struts2.StrutsConstants;
35  import org.apache.struts2.dispatcher.Dispatcher;
36  import org.apache.struts2.dispatcher.mapper.ActionMapping;
37  import org.apache.struts2.portlet.context.PortletActionContext;
38  import org.apache.struts2.portlet.util.PortletUrlHelper;
39  import org.apache.struts2.views.util.UrlHelper;
40  
41  import com.opensymphony.xwork2.ActionContext;
42  import com.opensymphony.xwork2.ActionInvocation;
43  import com.opensymphony.xwork2.ObjectFactory;
44  import com.opensymphony.xwork2.config.Configuration;
45  import com.opensymphony.xwork2.config.RuntimeConfiguration;
46  import com.opensymphony.xwork2.config.entities.ActionConfig;
47  import com.opensymphony.xwork2.config.entities.InterceptorMapping;
48  import com.opensymphony.xwork2.inject.Inject;
49  import com.opensymphony.xwork2.interceptor.MethodFilterInterceptorUtil;
50  import com.opensymphony.xwork2.util.ValueStack;
51  import com.opensymphony.xwork2.util.TextUtils;
52  import com.opensymphony.xwork2.validator.ActionValidatorManagerFactory;
53  import com.opensymphony.xwork2.validator.FieldValidator;
54  import com.opensymphony.xwork2.validator.ValidationInterceptor;
55  import com.opensymphony.xwork2.validator.Validator;
56  
57  /***
58   * <!-- START SNIPPET: javadoc -->
59   * <p/>
60   * Renders HTML an input form.<p/>
61   * <p/>
62   * The remote form allows the form to be submitted without the page being refreshed. The results from the form
63   * can be inserted into any HTML element on the page.<p/>
64   * <p/>
65   * NOTE:<p/>
66   * The order / logic in determining the posting url of the generated HTML form is as follows:-
67   * <ol>
68   * <li>
69   * If the action attribute is not specified, then the current request will be used to
70   * determine the posting url
71   * </li>
72   * <li>
73   * If the action is given, Struts will try to obtain an ActionConfig. This will be
74   * successfull if the action attribute is a valid action alias defined struts.xml.
75   * </li>
76   * <li>
77   * If the action is given and is not an action alias defined in struts.xml, Struts
78   * will used the action attribute as if it is the posting url, separting the namespace
79   * from it and using UrlHelper to generate the final url.
80   * </li>
81   * </ol>
82   * <p/>
83   * <!-- END SNIPPET: javadoc -->
84   * <p/>
85   * <p/> <b>Examples</b>
86   * <p/>
87   * <pre>
88   * <!-- START SNIPPET: example -->
89   * <p/>
90   * &lt;s:form ... /&gt;
91   * <p/>
92   * <!-- END SNIPPET: example -->
93   * </pre>
94   *
95   */
96  @StrutsTag(name="form", tldTagClass="org.apache.struts2.views.jsp.ui.FormTag", description="Renders an input form")
97  public class Form extends ClosingUIBean {
98      public static final String OPEN_TEMPLATE = "form";
99      public static final String TEMPLATE = "form-close";
100 
101     private int sequence = 0;
102 
103     protected String onsubmit;
104     protected String action;
105     protected String target;
106     protected String enctype;
107     protected String method;
108     protected String namespace;
109     protected String validate;
110     protected String portletMode;
111     protected String windowState;
112     protected String acceptcharset;
113 
114     protected boolean enableDynamicMethodInvocation = true;
115     protected Configuration configuration;
116     protected ObjectFactory objectFactory;
117 
118     public Form(ValueStack stack, HttpServletRequest request, HttpServletResponse response) {
119         super(stack, request, response);
120     }
121 
122     protected boolean evaluateNameValue() {
123         return false;
124     }
125 
126     public String getDefaultOpenTemplate() {
127         return OPEN_TEMPLATE;
128     }
129 
130     protected String getDefaultTemplate() {
131         return TEMPLATE;
132     }
133 
134     @Inject(StrutsConstants.STRUTS_ENABLE_DYNAMIC_METHOD_INVOCATION)
135     public void setEnableDynamicMethodInvocation(String enable) {
136         enableDynamicMethodInvocation = "true".equals(enable);
137     }
138 
139     @Inject
140     public void setConfiguration(Configuration configuration) {
141         this.configuration = configuration;
142     }
143 
144     @Inject
145     public void setObjectFactory(ObjectFactory objectFactory) {
146         this.objectFactory = objectFactory;
147     }
148 
149 
150     /*
151     * Revised for Portlet actionURL as form action, and add wwAction as hidden
152     * field. Refer to template.simple/form.vm
153     */
154     protected void evaluateExtraParams() {
155         super.evaluateExtraParams();
156 
157         //boolean isAjax = "ajax".equalsIgnoreCase(this.theme);
158 
159         if (validate != null) {
160             addParameter("validate", findValue(validate, Boolean.class));
161         }
162 
163         // calculate the action and namespace
164         /*String action = null;
165         if (this.action != null) {
166             // if it isn't specified, we'll make somethig up
167             action = findString(this.action);
168         }
169 
170         if (Dispatcher.getInstance().isPortletSupportActive() && PortletActionContext.isPortletRequest()) {
171             evaluateExtraParamsPortletRequest(namespace, action);
172         } else {
173             String namespace = determineNamespace(this.namespace, getStack(),
174                     request);
175             evaluateExtraParamsServletRequest(action, namespace, isAjax);
176         }*/
177 
178         if (onsubmit != null) {
179             addParameter("onsubmit", findString(onsubmit));
180         }
181 
182         if (target != null) {
183             addParameter("target", findString(target));
184         }
185 
186         if (enctype != null) {
187             addParameter("enctype", findString(enctype));
188         }
189 
190         if (method != null) {
191             addParameter("method", findString(method));
192         }
193 
194         if (acceptcharset != null) {
195             addParameter("acceptcharset", findString(acceptcharset));
196         }
197 
198         // keep a collection of the tag names for anything special the templates might want to do (such as pure client
199         // side validation)
200         if (!parameters.containsKey("tagNames")) {
201             // we have this if check so we don't do this twice (on open and close of the template)
202             addParameter("tagNames", new ArrayList());
203         }
204     }
205 
206     /***
207      * Form component determine the its HTML element id as follows:-
208      * <ol>
209      *    <li>if an 'id' attribute is specified.</li>
210      *    <li>if an 'action' attribute is specified, it will be used as the id.</li>
211      * </ol>
212      */
213     protected void populateComponentHtmlId(Form form) {
214         boolean isAjax = "ajax".equalsIgnoreCase(this.theme);
215 
216         String action = null;
217         if (this.action != null) {
218             // if it isn't specified, we'll make somethig up
219             action = findString(this.action);
220         }
221 
222         if (id != null) {
223             addParameter("id", escape(id));
224         }
225         if (Dispatcher.getInstance().isPortletSupportActive() && PortletActionContext.isPortletRequest()) {
226             evaluateExtraParamsPortletRequest(namespace, action);
227         } else {
228             String namespace = determineNamespace(this.namespace, getStack(),
229                     request);
230             evaluateExtraParamsServletRequest(action, namespace, isAjax);
231         }
232     }
233 
234     /***
235      * @param isAjax
236      * @param namespace
237      * @param action
238      */
239     private void evaluateExtraParamsServletRequest(String action, String namespace, boolean isAjax) {
240         if (action == null) {
241             // no action supplied? ok, then default to the current request (action or general URL)
242             ActionInvocation ai = (ActionInvocation) getStack().getContext().get(ActionContext.ACTION_INVOCATION);
243             if (ai != null) {
244                 action = ai.getProxy().getActionName();
245                 namespace = ai.getProxy().getNamespace();
246             } else {
247                 // hmm, ok, we need to just assume the current URL cut down
248                 String uri = request.getRequestURI();
249                 action = uri.substring(uri.lastIndexOf('/'));
250             }
251         }
252 
253         String actionMethod = "";
254         // FIXME: our implementation is flawed - the only concept of ! should be in DefaultActionMapper
255         // handle "name!method" convention.
256         if (enableDynamicMethodInvocation) {
257             if (action.indexOf("!") != -1) {
258                 int endIdx = action.lastIndexOf("!");
259                 actionMethod = action.substring(endIdx + 1, action.length());
260                 action = action.substring(0, endIdx);
261             }
262         }
263 
264         final ActionConfig actionConfig = configuration.getRuntimeConfiguration().getActionConfig(namespace, action);
265         String actionName = action;
266         if (actionConfig != null) {
267 
268             ActionMapping mapping = new ActionMapping(action, namespace, actionMethod, parameters);
269             String result = UrlHelper.buildUrl(actionMapper.getUriFromActionMapping(mapping), request, response, null);
270             addParameter("action", result);
271 
272             // let's try to get the actual action class and name
273             // this can be used for getting the list of validators
274             addParameter("actionName", actionName);
275             try {
276                 Class clazz = objectFactory.getClassInstance(actionConfig.getClassName());
277                 addParameter("actionClass", clazz);
278             } catch (ClassNotFoundException e) {
279                 // this is OK, we'll just move on
280             }
281 
282             addParameter("namespace", namespace);
283 
284             // if the name isn't specified, use the action name
285             if (name == null) {
286                 addParameter("name", action);
287             }
288 
289             // if the id isn't specified, use the action name
290             if (id == null) {
291                 addParameter("id", action);
292             }
293         } else if (action != null) {
294             // Since we can't find an action alias in the configuration, we just assume
295             // the action attribute supplied is the path to be used as the uri this
296             // form is submitting to.
297 
298             String result = UrlHelper.buildUrl(action, request, response, null);
299             addParameter("action", result);
300 
301             // namespace: cut out anything between the start and the last /
302             int slash = result.lastIndexOf('/');
303             if (slash != -1) {
304                 addParameter("namespace", result.substring(0, slash));
305             } else {
306                 addParameter("namespace", "");
307             }
308 
309             // name/id: cut out anything between / and . should be the id and name
310             if (id == null) {
311                 slash = result.lastIndexOf('/');
312                 int dot = result.indexOf('.', slash);
313                 if (dot != -1) {
314                     id = result.substring(slash + 1, dot);
315                 } else {
316                     id = result.substring(slash + 1);
317                 }
318                 addParameter("id", escape(id));
319             }
320         }
321 
322         // WW-1284
323         // evaluate if client-side js is to be enabled. (if validation interceptor
324         // does allow validation eg. method is not filtered out)
325         evaluateClientSideJsEnablement(actionName, namespace, actionMethod);
326     }
327 
328     private void evaluateClientSideJsEnablement(String actionName, String namespace, String actionMethod) {
329 
330         // Only evaluate if Client-Side js is to be enable when validate=true
331         Boolean validate = (Boolean) getParameters().get("validate");
332         if (validate != null && validate.booleanValue()) {
333 
334             addParameter("performValidation", Boolean.FALSE);
335 
336             RuntimeConfiguration runtimeConfiguration = configuration.getRuntimeConfiguration();
337             ActionConfig actionConfig = runtimeConfiguration.getActionConfig(namespace, actionName);
338 
339             if (actionConfig != null) {
340                 List interceptors = actionConfig.getInterceptors();
341                 for (Iterator i = interceptors.iterator(); i.hasNext();) {
342                     InterceptorMapping interceptorMapping = (InterceptorMapping) i.next();
343                     if (ValidationInterceptor.class.isInstance(interceptorMapping.getInterceptor())) {
344                         ValidationInterceptor validationInterceptor = (ValidationInterceptor) interceptorMapping.getInterceptor();
345 
346                         Set excludeMethods = validationInterceptor.getExcludeMethodsSet();
347                         Set includeMethods = validationInterceptor.getIncludeMethodsSet();
348 
349                         if (MethodFilterInterceptorUtil.applyMethod(excludeMethods, includeMethods, actionMethod)) {
350                             addParameter("performValidation", Boolean.TRUE);
351                         }
352                         return;
353                     }
354                 }
355             }
356         }
357     }
358 
359     /***
360      * Constructs the action url adapted to a portal environment.
361      *
362      * @param action The action to create the URL for.
363      */
364     private void evaluateExtraParamsPortletRequest(String namespace, String action) {
365 
366         if (this.action != null) {
367             // if it isn't specified, we'll make somethig up
368             action = findString(this.action);
369         }
370 
371         String type = "action";
372         if (TextUtils.stringSet(method)) {
373             if ("GET".equalsIgnoreCase(method.trim())) {
374                 type = "render";
375             }
376         }
377         if (action != null) {
378             String result = PortletUrlHelper.buildUrl(action, namespace,
379                     getParameters(), type, portletMode, windowState);
380             addParameter("action", result);
381 
382             // namespace: cut out anything between the start and the last /
383             int slash = result.lastIndexOf('/');
384             if (slash != -1) {
385                 addParameter("namespace", result.substring(0, slash));
386             } else {
387                 addParameter("namespace", "");
388             }
389 
390             // name/id: cut out anything between / and . should be the id and
391             // name
392             if (id == null) {
393                 slash = action.lastIndexOf('/');
394                 int dot = action.indexOf('.', slash);
395                 if (dot != -1) {
396                     id = action.substring(slash + 1, dot);
397                 } else {
398                     id = action.substring(slash + 1);
399                 }
400                 addParameter("id", escape(id));
401             }
402         }
403 
404     }
405 
406     public List getValidators(String name) {
407         Class actionClass = (Class) getParameters().get("actionClass");
408         if (actionClass == null) {
409             return Collections.EMPTY_LIST;
410         }
411 
412         List all = ActionValidatorManagerFactory.getInstance().getValidators(actionClass, (String) getParameters().get("actionName"));
413         List validators = new ArrayList();
414         for (Iterator iterator = all.iterator(); iterator.hasNext();) {
415             Validator validator = (Validator) iterator.next();
416             if (validator instanceof FieldValidator) {
417                 FieldValidator fieldValidator = (FieldValidator) validator;
418                 if (fieldValidator.getFieldName().equals(name)) {
419                     validators.add(fieldValidator);
420                 }
421             }
422         }
423 
424         return validators;
425     }
426 
427     /***
428      * Get a incrementing sequence unique to this <code>Form</code> component.
429      * It is used by <code>Form</code> component's child that might need a
430      * sequence to make them unique.
431      *
432      * @return int
433      */
434     protected int getSequence() {
435         return sequence++;
436     }
437 
438     @StrutsTagAttribute(description="HTML onsubmit attribute")
439     public void setOnsubmit(String onsubmit) {
440         this.onsubmit = onsubmit;
441     }
442 
443     @StrutsTagAttribute(description="Set action nane to submit to, without .action suffix", defaultValue="current action")
444     public void setAction(String action) {
445         this.action = action;
446     }
447 
448     @StrutsTagAttribute(description="HTML form target attribute")
449     public void setTarget(String target) {
450         this.target = target;
451     }
452 
453     @StrutsTagAttribute(description="HTML form enctype attribute")
454     public void setEnctype(String enctype) {
455         this.enctype = enctype;
456     }
457 
458     @StrutsTagAttribute(description="HTML form method attribute")
459     public void setMethod(String method) {
460         this.method = method;
461     }
462 
463     @StrutsTagAttribute(description="Namespace for action to submit to", defaultValue="current namespace")
464     public void setNamespace(String namespace) {
465         this.namespace = namespace;
466     }
467 
468     @StrutsTagAttribute(description="Whether client side/remote validation should be performed. Only" +
469                 " useful with theme xhtml/ajax", type="Boolean", defaultValue="false")
470     public void setValidate(String validate) {
471         this.validate = validate;
472     }
473 
474     @StrutsTagAttribute(description="he portlet mode to display after the form submit")
475     public void setPortletMode(String portletMode) {
476         this.portletMode = portletMode;
477     }
478 
479     @StrutsTagAttribute(description="The window state to display after the form submit")
480     public void setWindowState(String windowState) {
481         this.windowState = windowState;
482     }
483 
484     @StrutsTagAttribute(description="The accepted charsets for this form. The values may be comma or blank delimited.")
485     public void setAcceptcharset(String acceptcharset) {
486         this.acceptcharset = acceptcharset;
487     }
488 }