View Javadoc

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