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 }