zoukankan      html  css  js  c++  java
  • Struts(十九):类型转换、类型转换错误消息及显示

    • 类型转换概念

    1、从html表单页面到一个Action对象,类型转化是从字符串到一个非字符串:html并没有“类型”的概念,每个表单输入的信息都只可能是一个字符串或者一个字符串数组,但是在服务器端,必须把String字符串转化为一种特定的数据类型;

    2、在Struts2中,把请求参数映射到Action的属性的工作由ParametersInterceptor拦截器负责,它默认是defaultStack拦截器栈中的一员。Parameters拦截器可以自动完成字符串和基本类型之间转换。

    ParameterInterceptor:

      1 /*
      2  * Copyright 2002-2007,2009 The Apache Software Foundation.
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 package com.opensymphony.xwork2.interceptor;
     17 
     18 import com.opensymphony.xwork2.ActionContext;
     19 import com.opensymphony.xwork2.ActionInvocation;
     20 import com.opensymphony.xwork2.security.AcceptedPatternsChecker;
     21 import com.opensymphony.xwork2.security.ExcludedPatternsChecker;
     22 import com.opensymphony.xwork2.ValidationAware;
     23 import com.opensymphony.xwork2.XWorkConstants;
     24 import com.opensymphony.xwork2.conversion.impl.InstantiatingNullHandler;
     25 import com.opensymphony.xwork2.conversion.impl.XWorkConverter;
     26 import com.opensymphony.xwork2.inject.Inject;
     27 import com.opensymphony.xwork2.util.ClearableValueStack;
     28 import com.opensymphony.xwork2.util.LocalizedTextUtil;
     29 import com.opensymphony.xwork2.util.MemberAccessValueStack;
     30 import com.opensymphony.xwork2.util.ValueStack;
     31 import com.opensymphony.xwork2.util.ValueStackFactory;
     32 import com.opensymphony.xwork2.util.logging.Logger;
     33 import com.opensymphony.xwork2.util.logging.LoggerFactory;
     34 import com.opensymphony.xwork2.util.reflection.ReflectionContextState;
     35 
     36 import java.util.Collection;
     37 import java.util.Comparator;
     38 import java.util.Map;
     39 import java.util.TreeMap;
     40 
     41 
     42 /**
     43  * <!-- START SNIPPET: description -->
     44  * This interceptor sets all parameters on the value stack.
     45  *
     46  * This interceptor gets all parameters from {@link ActionContext#getParameters()} and sets them on the value stack by
     47  * calling {@link ValueStack#setValue(String, Object)}, typically resulting in the values submitted in a form
     48  * request being applied to an action in the value stack. Note that the parameter map must contain a String key and
     49  * often containers a String[] for the value.
     50  *
     51  * The interceptor takes one parameter named 'ordered'. When set to true action properties are guaranteed to be
     52  * set top-down which means that top action's properties are set first. Then it's subcomponents properties are set.
     53  * The reason for this order is to enable a 'factory' pattern. For example, let's assume that one has an action
     54  * that contains a property named 'modelClass' that allows to choose what is the underlying implementation of model.
     55  * By assuring that modelClass property is set before any model properties are set, it's possible to choose model
     56  * implementation during action.setModelClass() call. Similiarily it's possible to use action.setPrimaryKey()
     57  * property set call to actually load the model class from persistent storage. Without any assumption on parameter
     58  * order you have to use patterns like 'Preparable'.
     59  *
     60  * Because parameter names are effectively OGNL statements, it is important that security be taken in to account.
     61  * This interceptor will not apply any values in the parameters map if the expression contains an assignment (=),
     62  * multiple expressions (,), or references any objects in the context (#). This is all done in the {@link
     63  * #acceptableName(String)} method. In addition to this method, if the action being invoked implements the {@link
     64  * ParameterNameAware} interface, the action will be consulted to determine if the parameter should be set.
     65  *
     66  * In addition to these restrictions, a flag ({@link ReflectionContextState#DENY_METHOD_EXECUTION}) is set such that
     67  * no methods are allowed to be invoked. That means that any expression such as <i>person.doSomething()</i> or
     68  * <i>person.getName()</i> will be explicitely forbidden. This is needed to make sure that your application is not
     69  * exposed to attacks by malicious users.
     70  *
     71  * While this interceptor is being invoked, a flag ({@link ReflectionContextState#CREATE_NULL_OBJECTS}) is turned
     72  * on to ensure that any null reference is automatically created - if possible. See the type conversion documentation
     73  * and the {@link InstantiatingNullHandler} javadocs for more information.
     74  *
     75  * Finally, a third flag ({@link XWorkConverter#REPORT_CONVERSION_ERRORS}) is set that indicates any errors when
     76  * converting the the values to their final data type (String[] -&gt; int) an unrecoverable error occured. With this
     77  * flag set, the type conversion errors will be reported in the action context. See the type conversion documentation
     78  * and the {@link XWorkConverter} javadocs for more information.
     79  *
     80  * If you are looking for detailed logging information about your parameters, turn on DEBUG level logging for this
     81  * interceptor. A detailed log of all the parameter keys and values will be reported.
     82  *
     83  * <b>Note:</b> Since XWork 2.0.2, this interceptor extends {@link MethodFilterInterceptor}, therefore being
     84  * able to deal with excludeMethods / includeMethods parameters. See [Workflow Interceptor]
     85  * (class {@link DefaultWorkflowInterceptor}) for documentation and examples on how to use this feature.
     86  * <!-- END SNIPPET: description -->
     87  *
     88  * <u>Interceptor parameters:</u>
     89  *
     90  * <!-- START SNIPPET: parameters -->
     91  *
     92  * <ul>
     93  * <li>ordered - set to true if you want the top-down property setter behaviour</li>
     94  * <li>acceptParamNames - a comma delimited list of regular expressions to describe a whitelist of accepted parameter names.
     95  * Don't change the default unless you know what you are doing in terms of security implications</li>
     96  * <li>excludeParams - a comma delimited list of regular expressions to describe a blacklist of not allowed parameter names</li>
     97  * <li>paramNameMaxLength - the maximum length of parameter names; parameters with longer names will be ignored; the default is 100 characters</li>
     98  * </ul>
     99  *
    100  * <!-- END SNIPPET: parameters -->
    101  *
    102  *  <u>Extending the interceptor:</u>
    103  *
    104  * <!-- START SNIPPET: extending -->
    105  *
    106  *  The best way to add behavior to this interceptor is to utilize the {@link ParameterNameAware} interface in your
    107  * actions. However, if you wish to apply a global rule that isn't implemented in your action, then you could extend
    108  * this interceptor and override the {@link #acceptableName(String)} method.
    109  *
    110  * <!-- END SNIPPET: extending -->
    111  *
    112  *
    113  * <!-- START SNIPPET: extending-warning -->
    114  * Using {@link ParameterNameAware} could be dangerous as {@link ParameterNameAware#acceptableParameterName(String)} takes precedence
    115  * over ParametersInterceptor which means if ParametersInterceptor excluded given parameter name you can accept it with
    116  * {@link ParameterNameAware#acceptableParameterName(String)}.
    117  *
    118  * The best idea is to define very tight restrictions with ParametersInterceptor and relax them per action with
    119  * {@link ParameterNameAware#acceptableParameterName(String)}
    120  * <!-- END SNIPPET: extending-warning -->
    121  *
    122  *
    123  * <u>Example code:</u>
    124  *
    125  * <pre>
    126  * <!-- START SNIPPET: example -->
    127  * &lt;action name="someAction" class="com.examples.SomeAction"&gt;
    128  *     &lt;interceptor-ref name="params"/&gt;
    129  *     &lt;result name="success"&gt;good_result.ftl&lt;/result&gt;
    130  * &lt;/action&gt;
    131  * <!-- END SNIPPET: example -->
    132  * </pre>
    133  *
    134  * @author Patrick Lightbody
    135  */
    136 public class ParametersInterceptor extends MethodFilterInterceptor {
    137 
    138     private static final Logger LOG = LoggerFactory.getLogger(ParametersInterceptor.class);
    139 
    140     protected static final int PARAM_NAME_MAX_LENGTH = 100;
    141 
    142     private int paramNameMaxLength = PARAM_NAME_MAX_LENGTH;
    143     private boolean devMode = false;
    144 
    145     protected boolean ordered = false;
    146 
    147     private ValueStackFactory valueStackFactory;
    148     private ExcludedPatternsChecker excludedPatterns;
    149     private AcceptedPatternsChecker acceptedPatterns;
    150 
    151     @Inject
    152     public void setValueStackFactory(ValueStackFactory valueStackFactory) {
    153         this.valueStackFactory = valueStackFactory;
    154     }
    155 
    156     @Inject(XWorkConstants.DEV_MODE)
    157     public void setDevMode(String mode) {
    158         devMode = "true".equalsIgnoreCase(mode);
    159     }
    160 
    161     @Inject
    162     public void setExcludedPatterns(ExcludedPatternsChecker excludedPatterns) {
    163         this.excludedPatterns = excludedPatterns;
    164     }
    165 
    166     @Inject
    167     public void setAcceptedPatterns(AcceptedPatternsChecker acceptedPatterns) {
    168         this.acceptedPatterns = acceptedPatterns;
    169     }
    170 
    171     /**
    172      * If the param name exceeds the configured maximum length it will not be
    173      * accepted.
    174      *
    175      * @param paramNameMaxLength Maximum length of param names
    176      */
    177     public void setParamNameMaxLength(int paramNameMaxLength) {
    178         this.paramNameMaxLength = paramNameMaxLength;
    179     }
    180 
    181     static private int countOGNLCharacters(String s) {
    182         int count = 0;
    183         for (int i = s.length() - 1; i >= 0; i--) {
    184             char c = s.charAt(i);
    185             if (c == '.' || c == '[') count++;
    186         }
    187         return count;
    188     }
    189 
    190     /**
    191      * Compares based on number of '.' and '[' characters (fewer is higher)
    192      */
    193     static final Comparator<String> rbCollator = new Comparator<String>() {
    194         public int compare(String s1, String s2) {
    195             int l1 = countOGNLCharacters(s1),
    196                 l2 = countOGNLCharacters(s2);
    197             return l1 < l2 ? -1 : (l2 < l1 ? 1 : s1.compareTo(s2));
    198         }
    199 
    200     };
    201 
    202     @Override
    203     public String doIntercept(ActionInvocation invocation) throws Exception {
    204         Object action = invocation.getAction();
    205         if (!(action instanceof NoParameters)) {
    206             ActionContext ac = invocation.getInvocationContext();
    207             final Map<String, Object> parameters = retrieveParameters(ac);
    208 
    209             if (LOG.isDebugEnabled()) {
    210                 LOG.debug("Setting params " + getParameterLogMap(parameters));
    211             }
    212 
    213             if (parameters != null) {
    214                 Map<String, Object> contextMap = ac.getContextMap();
    215                 try {
    216                     ReflectionContextState.setCreatingNullObjects(contextMap, true);
    217                     ReflectionContextState.setDenyMethodExecution(contextMap, true);
    218                     ReflectionContextState.setReportingConversionErrors(contextMap, true);
    219 
    220                     ValueStack stack = ac.getValueStack();
    221                     setParameters(action, stack, parameters);
    222                 } finally {
    223                     ReflectionContextState.setCreatingNullObjects(contextMap, false);
    224                     ReflectionContextState.setDenyMethodExecution(contextMap, false);
    225                     ReflectionContextState.setReportingConversionErrors(contextMap, false);
    226                 }
    227             }
    228         }
    229         return invocation.invoke();
    230     }
    231 
    232     /**
    233      * Gets the parameter map to apply from wherever appropriate
    234      *
    235      * @param ac The action context
    236      * @return The parameter map to apply
    237      */
    238     protected Map<String, Object> retrieveParameters(ActionContext ac) {
    239         return ac.getParameters();
    240     }
    241 
    242 
    243     /**
    244      * Adds the parameters into context's ParameterMap
    245      *
    246      * @param ac        The action context
    247      * @param newParams The parameter map to apply
    248      *                  <p/>
    249      *                  In this class this is a no-op, since the parameters were fetched from the same location.
    250      *                  In subclasses both retrieveParameters() and addParametersToContext() should be overridden.
    251      */
    252     protected void addParametersToContext(ActionContext ac, Map<String, Object> newParams) {
    253     }
    254 
    255     protected void setParameters(final Object action, ValueStack stack, final Map<String, Object> parameters) {
    256         Map<String, Object> params;
    257         Map<String, Object> acceptableParameters;
    258         if (ordered) {
    259             params = new TreeMap<String, Object>(getOrderedComparator());
    260             acceptableParameters = new TreeMap<String, Object>(getOrderedComparator());
    261             params.putAll(parameters);
    262         } else {
    263             params = new TreeMap<String, Object>(parameters);
    264             acceptableParameters = new TreeMap<String, Object>();
    265         }
    266 
    267         for (Map.Entry<String, Object> entry : params.entrySet()) {
    268             String name = entry.getKey();
    269             Object value = entry.getValue();
    270             if (isAcceptableParameter(name, action)) {
    271                 acceptableParameters.put(name, entry.getValue());
    272             }
    273         }
    274 
    275         ValueStack newStack = valueStackFactory.createValueStack(stack);
    276         boolean clearableStack = newStack instanceof ClearableValueStack;
    277         if (clearableStack) {
    278             //if the stack's context can be cleared, do that to prevent OGNL
    279             //from having access to objects in the stack, see XW-641
    280             ((ClearableValueStack)newStack).clearContextValues();
    281             Map<String, Object> context = newStack.getContext();
    282             ReflectionContextState.setCreatingNullObjects(context, true);
    283             ReflectionContextState.setDenyMethodExecution(context, true);
    284             ReflectionContextState.setReportingConversionErrors(context, true);
    285 
    286             //keep locale from original context
    287             context.put(ActionContext.LOCALE, stack.getContext().get(ActionContext.LOCALE));
    288         }
    289 
    290         boolean memberAccessStack = newStack instanceof MemberAccessValueStack;
    291         if (memberAccessStack) {
    292             //block or allow access to properties
    293             //see WW-2761 for more details
    294             MemberAccessValueStack accessValueStack = (MemberAccessValueStack) newStack;
    295             accessValueStack.setAcceptProperties(acceptedPatterns.getAcceptedPatterns());
    296             accessValueStack.setExcludeProperties(excludedPatterns.getExcludedPatterns());
    297         }
    298 
    299         for (Map.Entry<String, Object> entry : acceptableParameters.entrySet()) {
    300             String name = entry.getKey();
    301             Object value = entry.getValue();
    302             try {
    303                 newStack.setParameter(name, value);
    304             } catch (RuntimeException e) {
    305                 if (devMode) {
    306                     notifyDeveloperParameterException(action, name, e.getMessage());
    307                 }
    308             }
    309         }
    310 
    311         if (clearableStack && (stack.getContext() != null) && (newStack.getContext() != null))
    312             stack.getContext().put(ActionContext.CONVERSION_ERRORS, newStack.getContext().get(ActionContext.CONVERSION_ERRORS));
    313 
    314         addParametersToContext(ActionContext.getContext(), acceptableParameters);
    315     }
    316 
    317     protected void notifyDeveloperParameterException(Object action, String property, String message) {
    318         String developerNotification = LocalizedTextUtil.findText(ParametersInterceptor.class, "devmode.notification",
    319                 ActionContext.getContext().getLocale(), "Developer Notification:
    {0}",
    320                 new Object[]{
    321                         "Unexpected Exception caught setting '" + property + "' on '" + action.getClass() + ": " + message
    322                 }
    323         );
    324         LOG.error(developerNotification);
    325         // see https://issues.apache.org/jira/browse/WW-4066
    326         if (action instanceof ValidationAware) {
    327             Collection<String> messages = ((ValidationAware) action).getActionMessages();
    328             messages.add(message);
    329             ((ValidationAware) action).setActionMessages(messages);
    330         }
    331     }
    332 
    333     /**
    334      * Checks if name of parameter can be accepted or thrown away
    335      *
    336      * @param name parameter name
    337      * @param action current action
    338      * @return true if parameter is accepted
    339      */
    340     protected boolean isAcceptableParameter(String name, Object action) {
    341         ParameterNameAware parameterNameAware = (action instanceof ParameterNameAware) ? (ParameterNameAware) action : null;
    342         return acceptableName(name) && (parameterNameAware == null || parameterNameAware.acceptableParameterName(name));
    343     }
    344 
    345     /**
    346      * Gets an instance of the comparator to use for the ordered sorting.  Override this
    347      * method to customize the ordering of the parameters as they are set to the
    348      * action.
    349      *
    350      * @return A comparator to sort the parameters
    351      */
    352     protected Comparator<String> getOrderedComparator() {
    353         return rbCollator;
    354     }
    355 
    356     protected String getParameterLogMap(Map<String, Object> parameters) {
    357         if (parameters == null) {
    358             return "NONE";
    359         }
    360 
    361         StringBuilder logEntry = new StringBuilder();
    362         for (Map.Entry entry : parameters.entrySet()) {
    363             logEntry.append(String.valueOf(entry.getKey()));
    364             logEntry.append(" => ");
    365             if (entry.getValue() instanceof Object[]) {
    366                 Object[] valueArray = (Object[]) entry.getValue();
    367                 logEntry.append("[ ");
    368                 if (valueArray.length > 0 ) {
    369                     for (int indexA = 0; indexA < (valueArray.length - 1); indexA++) {
    370                         Object valueAtIndex = valueArray[indexA];
    371                         logEntry.append(String.valueOf(valueAtIndex));
    372                         logEntry.append(", ");
    373                     }
    374                     logEntry.append(String.valueOf(valueArray[valueArray.length - 1]));
    375                 }
    376                 logEntry.append(" ] ");
    377             } else {
    378                 logEntry.append(String.valueOf(entry.getValue()));
    379             }
    380         }
    381 
    382         return logEntry.toString();
    383     }
    384 
    385     protected boolean acceptableName(String name) {
    386         boolean accepted = isWithinLengthLimit(name) && !isExcluded(name) && isAccepted(name);
    387         if (devMode && accepted) { // notify only when in devMode
    388             LOG.debug("Parameter [#0] was accepted and will be appended to action!", name);
    389         }
    390         return accepted;
    391     }
    392 
    393     protected boolean isWithinLengthLimit( String name ) {
    394         boolean matchLength = name.length() <= paramNameMaxLength;
    395         if (!matchLength) {
    396             notifyDeveloper("Parameter [#0] is too long, allowed length is [#1]", name, String.valueOf(paramNameMaxLength));
    397         }
    398         return matchLength;
    399     }
    400 
    401     protected boolean isAccepted(String paramName) {
    402         AcceptedPatternsChecker.IsAccepted result = acceptedPatterns.isAccepted(paramName);
    403         if (result.isAccepted()) {
    404             return true;
    405         }
    406         notifyDeveloper("Parameter [#0] didn't match accepted pattern [#1]!", paramName, result.getAcceptedPattern());
    407         return false;
    408     }
    409 
    410     protected boolean isExcluded(String paramName) {
    411         ExcludedPatternsChecker.IsExcluded result = excludedPatterns.isExcluded(paramName);
    412         if (result.isExcluded()) {
    413             notifyDeveloper("Parameter [#0] matches excluded pattern [#1]!", paramName, result.getExcludedPattern());
    414             return true;
    415         }
    416         return false;
    417     }
    418 
    419     private void notifyDeveloper(String message, String... parameters) {
    420         if (devMode) {
    421             LOG.warn(message, parameters);
    422         } else {
    423             if (LOG.isDebugEnabled()) {
    424                 LOG.debug(message, parameters);
    425             }
    426         }
    427     }
    428 
    429     /**
    430      * Whether to order the parameters or not
    431      *
    432      * @return True to order
    433      */
    434     public boolean isOrdered() {
    435         return ordered;
    436     }
    437 
    438     /**
    439      * Set whether to order the parameters by object depth or not
    440      *
    441      * @param ordered True to order them
    442      */
    443     public void setOrdered(boolean ordered) {
    444         this.ordered = ordered;
    445     }
    446 
    447     /**
    448      * Sets a comma-delimited list of regular expressions to match
    449      * parameters that are allowed in the parameter map (aka whitelist).
    450      * <p/>
    451      * Don't change the default unless you know what you are doing in terms
    452      * of security implications.
    453      *
    454      * @param commaDelim A comma-delimited list of regular expressions
    455      */
    456     public void setAcceptParamNames(String commaDelim) {
    457         acceptedPatterns.setAcceptedPatterns(commaDelim);
    458     }
    459 
    460     /**
    461      * Sets a comma-delimited list of regular expressions to match
    462      * parameters that should be removed from the parameter map.
    463      *
    464      * @param commaDelim A comma-delimited list of regular expressions
    465      */
    466     public void setExcludeParams(String commaDelim) {
    467         excludedPatterns.setExcludedPatterns(commaDelim);
    468     }
    469 
    470 }
    View Code
    • 类型转换错误消息及显示

    如果类型转换失败:

    1、若Action类没有实现ValidationAware接口:Struts在遇到类型转换错误时,仍会调用其Action的方法,就好像什么都没有发生一样;

    struts.xml

     1 <?xml version="1.0" encoding="UTF-8" ?>
     2 <!DOCTYPE struts PUBLIC
     3     "-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
     4     "http://struts.apache.org/dtds/struts-2.3.dtd">
     5 <struts>
     6     <constant name="struts.ognl.allowStaticMethodAccess" value="true" />
     7     <constant name="struts.devMode" value="false" />
     8     <package name="default" namespace="/" extends="struts-default">
     9         <global-results>
    10             <result name="error">/error.jsp</result>
    11         </global-results>
    12         <global-exception-mappings>
    13             <exception-mapping result="error"
    14                 exception="java.lang.ArithmeticException"></exception-mapping>
    15         </global-exception-mappings>
    16         <action name="myAction" class="com.dx.actions.MyAction" method="save">
    17             <result>/success.jsp</result>
    18         </action>
    19     </package>
    20 </struts>
    View Code

    MyAction.java

     1 /**
     2  * @author Administrator
     3  *
     4  */
     5 package com.dx.actions;
     6 
     7 public class MyAction {
     8     private Integer age;
     9     public Integer getAge() {
    10         return age;
    11     }
    12     public void setAge(Integer age) {
    13         this.age = age;
    14     }
    15     
    16     public String save() {
    17 
    18         return "success";
    19     }
    20 }
    View Code

    index.jsp

    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <%@ taglib prefix="s" uri="/struts-tags"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Insert title here</title>
    </head>
    <body>
        <s:form action="myAction">
            <s:textfield name="age" label="Age"></s:textfield>
            <s:submit label="提交"></s:submit>
        </s:form>
    </body>
    </html>
    View Code

    访问index.jsp,输入“abc”则出现错误,但是依然调转到success.jsp.

    2、若Action类实现ValidationAware接口:Struts在遇到类型转换错误时,将不会继续调用其Action方法:Struts将检查相关action元素的声明是否包含这一个name=input的result。如果有,Struts将把控制权转交给那个result元素;如没有input result,Struts将抛出一个异常。

    修改MyAction.java页面,使其继承com.opensymphony.xwork2.ActionSupport类,因为com.opensymphony.xwork2.ActionSupport实现了com.opensymphony.xwork2.ValidationAware接口。

    /**
     * @author Administrator
     *
     */
    package com.dx.actions;
    
    import com.opensymphony.xwork2.ActionSupport;
    
    public class MyAction extends ActionSupport {
        private Integer age;
        public Integer getAge() {
            return age;
        }
        public void setAge(Integer age) {
            this.age = age;
        }
        
        public String save() {
    
            return "success";
        }
    }
    View Code

    com.opensymphony.xwork2.ActionSupport

    /*
     * Copyright 2002-2006,2009 The Apache Software Foundation.
     * 
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     * 
     *      http://www.apache.org/licenses/LICENSE-2.0
     * 
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */
    package com.opensymphony.xwork2;
    
    import com.opensymphony.xwork2.inject.Container;
    import com.opensymphony.xwork2.inject.Inject;
    import com.opensymphony.xwork2.util.ValueStack;
    import com.opensymphony.xwork2.util.logging.Logger;
    import com.opensymphony.xwork2.util.logging.LoggerFactory;
    
    import java.io.Serializable;
    import java.util.Arrays;
    import java.util.Collection;
    import java.util.List;
    import java.util.Locale;
    import java.util.Map;
    import java.util.ResourceBundle;
    
    
    /**
     * Provides a default implementation for the most common actions.
     * See the documentation for all the interfaces this class implements for more detailed information.
     */
    public class ActionSupport implements Action, Validateable, ValidationAware, TextProvider, LocaleProvider, Serializable {
    
        protected static Logger LOG = LoggerFactory.getLogger(ActionSupport.class);
    
        private final ValidationAwareSupport validationAware = new ValidationAwareSupport();
    
        private transient TextProvider textProvider;
        private Container container;
    
        public void setActionErrors(Collection<String> errorMessages) {
            validationAware.setActionErrors(errorMessages);
        }
    
        public Collection<String> getActionErrors() {
            return validationAware.getActionErrors();
        }
    
        public void setActionMessages(Collection<String> messages) {
            validationAware.setActionMessages(messages);
        }
    
        public Collection<String> getActionMessages() {
            return validationAware.getActionMessages();
        }
    
        /**
         * @deprecated Use {@link #getActionErrors()}.
         */
        @Deprecated
        public Collection<String> getErrorMessages() {
            return getActionErrors();
        }
    
        /**
         * @deprecated Use {@link #getFieldErrors()}.
         */
        @Deprecated
        public Map<String, List<String>> getErrors() {
            return getFieldErrors();
        }
    
        public void setFieldErrors(Map<String, List<String>> errorMap) {
            validationAware.setFieldErrors(errorMap);
        }
    
        public Map<String, List<String>> getFieldErrors() {
            return validationAware.getFieldErrors();
        }
    
        public Locale getLocale() {
            ActionContext ctx = ActionContext.getContext();
            if (ctx != null) {
                return ctx.getLocale();
            } else {
                if (LOG.isDebugEnabled()) {
                LOG.debug("Action context not initialized");
                }
                return null;
            }
        }
    
        public boolean hasKey(String key) {
            return getTextProvider().hasKey(key);
        }
    
        public String getText(String aTextName) {
            return getTextProvider().getText(aTextName);
        }
    
        public String getText(String aTextName, String defaultValue) {
            return getTextProvider().getText(aTextName, defaultValue);
        }
    
        public String getText(String aTextName, String defaultValue, String obj) {
            return getTextProvider().getText(aTextName, defaultValue, obj);
        }
    
        public String getText(String aTextName, List<?> args) {
            return getTextProvider().getText(aTextName, args);
        }
    
        public String getText(String key, String[] args) {
            return getTextProvider().getText(key, args);
        }
    
        public String getText(String aTextName, String defaultValue, List<?> args) {
            return getTextProvider().getText(aTextName, defaultValue, args);
        }
    
        public String getText(String key, String defaultValue, String[] args) {
            return getTextProvider().getText(key, defaultValue, args);
        }
    
        public String getText(String key, String defaultValue, List<?> args, ValueStack stack) {
            return getTextProvider().getText(key, defaultValue, args, stack);
        }
    
        public String getText(String key, String defaultValue, String[] args, ValueStack stack) {
            return getTextProvider().getText(key, defaultValue, args, stack);
        }
    
        /**
         * Dedicated method to support I10N and conversion errors
         *
         * @param key message which contains formatting string
         * @param expr that should be formatted
         * @return formatted expr with format specified by key
         */
        public String getFormatted(String key, String expr) {
            Map<String, Object> conversionErrors = ActionContext.getContext().getConversionErrors();
            if (conversionErrors.containsKey(expr)) {
                String[] vals = (String[]) conversionErrors.get(expr);
                return vals[0];
            } else {
                final ValueStack valueStack = ActionContext.getContext().getValueStack();
                final Object val = valueStack.findValue(expr);
                return getText(key, Arrays.asList(val));
            }
        }
    
        public ResourceBundle getTexts() {
            return getTextProvider().getTexts();
        }
    
        public ResourceBundle getTexts(String aBundleName) {
            return getTextProvider().getTexts(aBundleName);
        }
    
        public void addActionError(String anErrorMessage) {
            validationAware.addActionError(anErrorMessage);
        }
    
        public void addActionMessage(String aMessage) {
            validationAware.addActionMessage(aMessage);
        }
    
        public void addFieldError(String fieldName, String errorMessage) {
            validationAware.addFieldError(fieldName, errorMessage);
        }
    
        public String input() throws Exception {
            return INPUT;
        }
    
        /**
         * A default implementation that does nothing an returns "success".
         * <p/>
         * Subclasses should override this method to provide their business logic.
         * <p/>
         * See also {@link com.opensymphony.xwork2.Action#execute()}.
         *
         * @return returns {@link #SUCCESS}
         * @throws Exception can be thrown by subclasses.
         */
        public String execute() throws Exception {
            return SUCCESS;
        }
    
        public boolean hasActionErrors() {
            return validationAware.hasActionErrors();
        }
    
        public boolean hasActionMessages() {
            return validationAware.hasActionMessages();
        }
    
        public boolean hasErrors() {
            return validationAware.hasErrors();
        }
    
        public boolean hasFieldErrors() {
            return validationAware.hasFieldErrors();
        }
    
        /**
         * Clears field errors. Useful for Continuations and other situations
         * where you might want to clear parts of the state on the same action.
         */
        public void clearFieldErrors() {
            validationAware.clearFieldErrors();
        }
    
        /**
         * Clears action errors. Useful for Continuations and other situations
         * where you might want to clear parts of the state on the same action.
         */
        public void clearActionErrors() {
            validationAware.clearActionErrors();
        }
    
        /**
         * Clears messages. Useful for Continuations and other situations
         * where you might want to clear parts of the state on the same action.
         */
        public void clearMessages() {
            validationAware.clearMessages();
        }
    
        /**
         * Clears all errors. Useful for Continuations and other situations
         * where you might want to clear parts of the state on the same action.
         */
        public void clearErrors() {
            validationAware.clearErrors();
        }
    
        /**
         * Clears all errors and messages. Useful for Continuations and other situations
         * where you might want to clear parts of the state on the same action.
         */
        public void clearErrorsAndMessages() {
            validationAware.clearErrorsAndMessages();
        }
    
        /**
         * A default implementation that validates nothing.
         * Subclasses should override this method to provide validations.
         */
        public void validate() {
        }
    
        @Override
        public Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    
        /**
         * <!-- START SNIPPET: pause-method -->
         * Stops the action invocation immediately (by throwing a PauseException) and causes the action invocation to return
         * the specified result, such as {@link #SUCCESS}, {@link #INPUT}, etc.
         * <p/>
         * <p/>
         * The next time this action is invoked (and using the same continuation ID), the method will resume immediately
         * after where this method was called, with the entire call stack in the execute method restored.
         * <p/>
         * <p/>
         * Note: this method can <b>only</b> be called within the {@link #execute()} method.
         * <!-- END SNIPPET: pause-method -->
         *
         * @param result the result to return - the same type of return value in the {@link #execute()} method.
         */
        public void pause(String result) {
        }
    
        /**
         * If called first time it will create {@link com.opensymphony.xwork2.TextProviderFactory},
         * inject dependency (if {@link com.opensymphony.xwork2.inject.Container} is accesible) into in,
         * then will create new {@link com.opensymphony.xwork2.TextProvider} and store it in a field
         * for future references and at the returns reference to that field
         *
         * @return reference to field with TextProvider
         */
        private TextProvider getTextProvider() {
            if (textProvider == null) {
                TextProviderFactory tpf = new TextProviderFactory();
                if (container != null) {
                    container.inject(tpf);
                }
                textProvider = tpf.createInstance(getClass(), this);
            }
            return textProvider;
        }
    
        @Inject
        public void setContainer(Container container) {
            this.container = container;
        }
    
    }
    View Code

    com.opensymphony.xwork2.ValidationAware

    /*
     * Copyright 2002-2007,2009 The Apache Software Foundation.
     * 
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     * 
     *      http://www.apache.org/licenses/LICENSE-2.0
     * 
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */
    package com.opensymphony.xwork2;
    
    import java.util.Collection;
    import java.util.List;
    import java.util.Map;
    
    /**
     * ValidationAware classes can accept Action (class level) or field level error messages. Action level messages are kept
     * in a Collection. Field level error messages are kept in a Map from String field name to a List of field error msgs.
     *
     * @author plightbo 
     */
    public interface ValidationAware {
    
        /**
         * Set the Collection of Action-level String error messages.
         *
         * @param errorMessages Collection of String error messages
         */
        void setActionErrors(Collection<String> errorMessages);
    
        /**
         * Get the Collection of Action-level error messages for this action. Error messages should not
         * be added directly here, as implementations are free to return a new Collection or an
         * Unmodifiable Collection.
         *
         * @return Collection of String error messages
         */
        Collection<String> getActionErrors();
    
        /**
         * Set the Collection of Action-level String messages (not errors).
         *
         * @param messages Collection of String messages (not errors).
         */
        void setActionMessages(Collection<String> messages);
    
        /**
         * Get the Collection of Action-level messages for this action. Messages should not be added
         * directly here, as implementations are free to return a new Collection or an Unmodifiable
         * Collection.
         *
         * @return Collection of String messages
         */
        Collection<String> getActionMessages();
    
        /**
         * Set the field error map of fieldname (String) to Collection of String error messages.
         *
         * @param errorMap field error map
         */
        void setFieldErrors(Map<String, List<String>> errorMap);
    
        /**
         * Get the field specific errors associated with this action. Error messages should not be added
         * directly here, as implementations are free to return a new Collection or an Unmodifiable
         * Collection.
         *
         * @return Map with errors mapped from fieldname (String) to Collection of String error messages
         */
        Map<String, List<String>> getFieldErrors();
    
        /**
         * Add an Action-level error message to this Action.
         *
         * @param anErrorMessage  the error message
         */
        void addActionError(String anErrorMessage);
    
        /**
         * Add an Action-level message to this Action.
         *
         * @param aMessage  the message
         */
        void addActionMessage(String aMessage);
    
        /**
         * Add an error message for a given field.
         *
         * @param fieldName    name of field
         * @param errorMessage the error message
         */
        void addFieldError(String fieldName, String errorMessage);
    
        /**
         * Check whether there are any Action-level error messages.
         *
         * @return true if any Action-level error messages have been registered
         */
        boolean hasActionErrors();
    
        /**
         * Checks whether there are any Action-level messages.
         *
         * @return true if any Action-level messages have been registered
         */
        boolean hasActionMessages();
    
        /**
         * Checks whether there are any action errors or field errors.
         * <p/>
         * <b>Note</b>: that this does not have the same meaning as in WW 1.x.
         *
         * @return <code>(hasActionErrors() || hasFieldErrors())</code>
         */
        boolean hasErrors();
    
        /**
         * Check whether there are any field errors associated with this action.
         *
         * @return whether there are any field errors
         */
        boolean hasFieldErrors();
    
    }
    View Code

    访问index.jsp,输入“abc”则出现错误,但是发现跳转到404错误页面(而且后台抛出了异常信息):

     修改struts.xml

            <action name="myAction" class="com.dx.actions.MyAction" method="save">
                <result>/success.jsp</result>
                <result name="input">/index.jsp</result>
            </action>

    访问index.jsp,输入“abc”则出现错误,但是发现跳转到index.jsp页面(而且后台并没有抛出了异常信息)

    •  类型转换错误消息的定制:

    1、作为默认的defaultStack拦截器栈的一员,com.opensymphony.xwork2.interceptor.ConversionErrorInterceptor

    /*
     * Copyright 2002-2007,2009 The Apache Software Foundation.
     * 
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     * 
     *      http://www.apache.org/licenses/LICENSE-2.0
     * 
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */
    package com.opensymphony.xwork2.interceptor;
    
    import com.opensymphony.xwork2.ActionContext;
    import com.opensymphony.xwork2.ActionInvocation;
    import com.opensymphony.xwork2.ValidationAware;
    import com.opensymphony.xwork2.conversion.impl.XWorkConverter;
    import com.opensymphony.xwork2.util.ValueStack;
    import org.apache.commons.lang3.StringEscapeUtils;
    
    import java.util.HashMap;
    import java.util.Map;
    
    
    /**
     * <!-- START SNIPPET: description -->
     * ConversionErrorInterceptor adds conversion errors from the ActionContext to the Action's field errors.
     *
     * <p/>
     * This interceptor adds any error found in the {@link ActionContext}'s conversionErrors map as a field error (provided
     * that the action implements {@link ValidationAware}). In addition, any field that contains a validation error has its
     * original value saved such that any subsequent requests for that value return the original value rather than the value
     * in the action. This is important because if the value "abc" is submitted and can't be converted to an int, we want to
     * display the original string ("abc") again rather than the int value (likely 0, which would make very little sense to
     * the user).
     *
     *
     * <!-- END SNIPPET: description -->
     *
     * <p/> <u>Interceptor parameters:</u>
     *
     * <!-- START SNIPPET: parameters -->
     *
     * <ul>
     *
     * <li>None</li>
     *
     * </ul>
     *
     * <!-- END SNIPPET: parameters -->
     *
     * <p/> <u>Extending the interceptor:</u>
     *
     * <p/>
     *
     * <!-- START SNIPPET: extending -->
     *
     * Because this interceptor is not web-specific, it abstracts the logic for whether an error should be added. This
     * allows for web-specific interceptors to use more complex logic in the {@link #shouldAddError} method for when a value
     * has a conversion error but is null or empty or otherwise indicates that the value was never actually entered by the
     * user.
     *
     * <!-- END SNIPPET: extending -->
     *
     * <p/> <u>Example code:</u>
     *
     * <pre>
     * <!-- START SNIPPET: example -->
     * &lt;action name="someAction" class="com.examples.SomeAction"&gt;
     *     &lt;interceptor-ref name="params"/&gt;
     *     &lt;interceptor-ref name="conversionError"/&gt;
     *     &lt;result name="success"&gt;good_result.ftl&lt;/result&gt;
     * &lt;/action&gt;
     * <!-- END SNIPPET: example -->
     * </pre>
     *
     * @author Jason Carreira
     */
    public class ConversionErrorInterceptor extends AbstractInterceptor {
    
        public static final String ORIGINAL_PROPERTY_OVERRIDE = "original.property.override";
    
        protected Object getOverrideExpr(ActionInvocation invocation, Object value) {
            return escape(value);
        }
    
        protected String escape(Object value) {
            return """ + StringEscapeUtils.escapeJava(String.valueOf(value)) + """;
        }
    
        @Override
        public String intercept(ActionInvocation invocation) throws Exception {
    
            ActionContext invocationContext = invocation.getInvocationContext();
            Map<String, Object> conversionErrors = invocationContext.getConversionErrors();
            ValueStack stack = invocationContext.getValueStack();
    
            HashMap<Object, Object> fakie = null;
    
            for (Map.Entry<String, Object> entry : conversionErrors.entrySet()) {
                String propertyName = entry.getKey();
                Object value = entry.getValue();
    
                if (shouldAddError(propertyName, value)) {
                    String message = XWorkConverter.getConversionErrorMessage(propertyName, stack);
    
                    Object action = invocation.getAction();
                    if (action instanceof ValidationAware) {
                        ValidationAware va = (ValidationAware) action;
                        va.addFieldError(propertyName, message);
                    }
    
                    if (fakie == null) {
                        fakie = new HashMap<Object, Object>();
                    }
    
                    fakie.put(propertyName, getOverrideExpr(invocation, value));
                }
            }
    
            if (fakie != null) {
                // if there were some errors, put the original (fake) values in place right before the result
                stack.getContext().put(ORIGINAL_PROPERTY_OVERRIDE, fakie);
                invocation.addPreResultListener(new PreResultListener() {
                    public void beforeResult(ActionInvocation invocation, String resultCode) {
                        Map<Object, Object> fakie = (Map<Object, Object>) invocation.getInvocationContext().get(ORIGINAL_PROPERTY_OVERRIDE);
    
                        if (fakie != null) {
                            invocation.getStack().setExprOverrides(fakie);
                        }
                    }
                });
            }
            return invocation.invoke();
        }
    
        protected boolean shouldAddError(String propertyName, Object value) {
            return true;
        }
    }
    View Code

    拦截器负责添加与类型转化有关的错误(前提Action类必须实现了ValidationAware接口)和保存各请求参数的原始值。

    2、若字段标签使用的不是Simple主题,则非法输入字段将导致有一条以下格式的出错信息:

    Invalid field value for field "fieldName".

    如果使用simple主题时,及不会出现提示错误信息:

    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <%@ taglib prefix="s" uri="/struts-tags"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Insert title here</title>
    </head>
    <body>
        <s:debug></s:debug>
        <s:form action="myAction" theme="simple">
            <s:textfield name="age" label="Age"></s:textfield>
            <s:submit label="提交"></s:submit>
        </s:form>
    </body>
    </html>

    但输入“abc”时:

    3、覆盖默认的出错信息:

    -在对应的Action类所在的包中新建ActionClassName.properties文件,ClassName即为包含着输入字段的Action类的类名;

    -在该属性文件中添加如下键值对:invalid.fieldvalue.fieldname=xxx

    invalid.fieldvalue.age=错误的年龄格式

    4、定制出错误的格式:

    -每一条出错误都被打包在一个html span元素里,可以通过覆盖其行标为errorMessage的那个css样式改变出错误的格式;

    -新建template.simple包,新建filederror.ftl文件,把struts2-core.jar下的template.simple下的filederror.ftl文件内容拷贝到新建的fielderror.ftl中,并对其进行编辑。

    5、显示错误消息:如果simple主题,可以通过EL(${fieldError})或者<s:fielderror fieldName="fieldName"></s:fielderror>标签显示错误消息。

    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <%@ taglib prefix="s" uri="/struts-tags"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Insert title here</title>
    </head>
    <body>
        <s:debug></s:debug>
        <s:form action="myAction" theme="simple">
            <s:textfield name="age" label="Age"></s:textfield>${fieldErrors.age[0]}*
            <s:fielderror fieldName="age"></s:fielderror>
            <s:submit label="提交"></s:submit>
        </s:form>
    </body>
    </html>

    访问index.jsp,输入“abc”则出现错误,但是发现跳转到index.jsp页面:

  • 相关阅读:
    scala之伴生对象的继承
    scala之伴生对象说明
    “Failed to install the following Android SDK packages as some licences have not been accepted” 错误
    PATH 环境变量重复问题解决
    Ubuntu 18.04 配置java环境
    JDBC的基本使用2
    DCL的基本语法(授权)
    ZJNU 1374
    ZJNU 2184
    ZJNU 1334
  • 原文地址:https://www.cnblogs.com/yy3b2007com/p/6654340.html
Copyright © 2011-2022 走看看