View Javadoc

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