Save This Page
Home » xwork-2.1.1-src » com.opensymphony.xwork2.interceptor » [javadoc | source]
    1   /*
    2    * Copyright (c) 2002-2007 by OpenSymphony
    3    * All rights reserved.
    4    */
    5   package com.opensymphony.xwork2.interceptor;
    6   
    7   import java.util.Collection;
    8   import java.util.Collections;
    9   import java.util.Comparator;
   10   import java.util.HashSet;
   11   import java.util.Iterator;
   12   import java.util.Map;
   13   import java.util.Set;
   14   import java.util.TreeMap;
   15   import java.util.regex.Matcher;
   16   import java.util.regex.Pattern;
   17   
   18   import com.opensymphony.xwork2.ActionContext;
   19   import com.opensymphony.xwork2.ActionInvocation;
   20   import com.opensymphony.xwork2.ValidationAware;
   21   import com.opensymphony.xwork2.conversion.impl.InstantiatingNullHandler;
   22   import com.opensymphony.xwork2.conversion.impl.XWorkConverter;
   23   import com.opensymphony.xwork2.inject.Inject;
   24   import com.opensymphony.xwork2.util.LocalizedTextUtil;
   25   import com.opensymphony.xwork2.util.TextParseUtil;
   26   import com.opensymphony.xwork2.util.ValueStack;
   27   import com.opensymphony.xwork2.util.logging.Logger;
   28   import com.opensymphony.xwork2.util.logging.LoggerFactory;
   29   import com.opensymphony.xwork2.util.reflection.ReflectionContextState;
   30   
   31   
   32   /**
   33    * <!-- START SNIPPET: description -->
   34    * This interceptor sets all parameters on the value stack.
   35    * <p/>
   36    * This interceptor gets all parameters from {@link ActionContext#getParameters()} and sets them on the value stack by
   37    * calling {@link ValueStack#setValue(String, Object)}, typically resulting in the values submitted in a form
   38    * request being applied to an action in the value stack. Note that the parameter map must contain a String key and
   39    * often containers a String[] for the value.
   40    * 
   41    * <p/> The interceptor takes one parameter named 'ordered'. When set to true action properties are guaranteed to be
   42    * set top-down which means that top action's properties are set first. Then it's subcomponents properties are set.
   43    * The reason for this order is to enable a 'factory' pattern. For example, let's assume that one has an action 
   44    * that contains a property named 'modelClass' that allows to choose what is the underlying implementation of model.
   45    * By assuring that modelClass property is set before any model properties are set, it's possible to choose model
   46    * implementation during action.setModelClass() call. Similiarily it's possible to use action.setPrimaryKey() 
   47    * property set call to actually load the model class from persistent storage. Without any assumption on parameter 
   48    * order you have to use patterns like 'Preparable'.
   49    *  
   50    * <p/> Because parameter names are effectively OGNL statements, it is important that security be taken in to account.
   51    * This interceptor will not apply any values in the parameters map if the expression contains an assignment (=),
   52    * multiple expressions (,), or references any objects in the context (#). This is all done in the {@link
   53    * #acceptableName(String)} method. In addition to this method, if the action being invoked implements the {@link
   54    * ParameterNameAware} interface, the action will be consulted to determine if the parameter should be set.
   55    *
   56    * <p/> In addition to these restrictions, a flag ({@link ReflectionContextState#DENY_METHOD_EXECUTION}) is set such that
   57    * no methods are allowed to be invoked. That means that any expression such as <i>person.doSomething()</i> or
   58    * <i>person.getName()</i> will be explicitely forbidden. This is needed to make sure that your application is not
   59    * exposed to attacks by malicious users.
   60    *
   61    * <p/> While this interceptor is being invoked, a flag ({@link ReflectionContextState#CREATE_NULL_OBJECTS}) is turned
   62    * on to ensure that any null reference is automatically created - if possible. See the type conversion documentation
   63    * and the {@link InstantiatingNullHandler} javadocs for more information.
   64    *
   65    * <p/> Finally, a third flag ({@link XWorkConverter#REPORT_CONVERSION_ERRORS}) is set that indicates any errors when
   66    * converting the the values to their final data type (String[] -&gt; int) an unrecoverable error occured. With this
   67    * flag set, the type conversion errors will be reported in the action context. See the type conversion documentation
   68    * and the {@link XWorkConverter} javadocs for more information.
   69    *
   70    * <p/> If you are looking for detailed logging information about your parameters, turn on DEBUG level logging for this
   71    * interceptor. A detailed log of all the parameter keys and values will be reported.
   72    *
   73    * <p/>
   74    * <b>Note:</b> Since XWork 2.0.2, this interceptor extends {@link MethodFilterInterceptor}, therefore being
   75    * able to deal with excludeMethods / includeMethods parameters. See [Workflow Interceptor]
   76    * (class {@link DefaultWorkflowInterceptor}) for documentation and examples on how to use this feature.
   77    *
   78    * <!-- END SNIPPET: description -->
   79    *
   80    * <p/> <u>Interceptor parameters:</u>
   81    *
   82    * <!-- START SNIPPET: parameters -->
   83    *
   84    * <ul>
   85    *
   86    * <li>ordered - set to true if you want the top-down property setter behaviour</li>
   87    *
   88    * </ul>
   89    *
   90    * <!-- END SNIPPET: parameters -->
   91    *
   92    * <p/> <u>Extending the interceptor:</u>
   93    *
   94    * <!-- START SNIPPET: extending -->
   95    *
   96    * <p/> The best way to add behavior to this interceptor is to utilize the {@link ParameterNameAware} interface in your
   97    * actions. However, if you wish to apply a global rule that isn't implemented in your action, then you could extend
   98    * this interceptor and override the {@link #acceptableName(String)} method.
   99    *
  100    * <!-- END SNIPPET: extending -->
  101    *
  102    * <p/> <u>Example code:</u>
  103    *
  104    * <pre>
  105    * <!-- START SNIPPET: example -->
  106    * &lt;action name="someAction" class="com.examples.SomeAction"&gt;
  107    *     &lt;interceptor-ref name="params"/&gt;
  108    *     &lt;result name="success"&gt;good_result.ftl&lt;/result&gt;
  109    * &lt;/action&gt;
  110    * <!-- END SNIPPET: example -->
  111    * </pre>
  112    *
  113    * @author Patrick Lightbody
  114    */
  115   public class ParametersInterceptor extends MethodFilterInterceptor {
  116   
  117       private static final Logger LOG = LoggerFactory.getLogger(ParametersInterceptor.class);
  118   
  119       boolean ordered = false;
  120       Set<Pattern> excludeParams = Collections.EMPTY_SET;
  121       static boolean devMode = false;
  122       
  123       @Inject("devMode")
  124       public static void setDevMode(String mode) {
  125           devMode = "true".equals(mode);
  126       }
  127       
  128       /** Compares based on number of '.' characters (fewer is higher) */
  129       static final Comparator rbCollator = new Comparator() {
  130           public int compare(Object arg0, Object arg1) {
  131               String s1 = (String) arg0;
  132               String s2 = (String) arg1;
  133               int l1=0, l2=0;
  134               for( int i=s1.length()-1; i>=0; i--) {
  135                   if( s1.charAt(i) == '.') l1++;
  136               }
  137               for( int i=s2.length()-1; i>=0; i--) {
  138                   if( s2.charAt(i) == '.') l2++;
  139               }
  140               return l1 < l2 ? -1 : ( l2 < l1 ? 1 : s1.compareTo(s2));
  141           }
  142       };
  143       
  144       public String doIntercept(ActionInvocation invocation) throws Exception {
  145           Object action = invocation.getAction();
  146           if (!(action instanceof NoParameters)) {
  147               ActionContext ac = invocation.getInvocationContext();
  148               final Map parameters = retrieveParametersFromContext(ac);
  149   
  150               if (LOG.isDebugEnabled()) {
  151                   LOG.debug("Setting params " + getParameterLogMap(parameters));
  152               }
  153   
  154               if (parameters != null) {
  155               	Map contextMap = ac.getContextMap();
  156                   try {
  157                   	ReflectionContextState.setCreatingNullObjects(contextMap, true);
  158                   	ReflectionContextState.setDenyMethodExecution(contextMap, true);
  159                   	ReflectionContextState.setReportingConversionErrors(contextMap, true);
  160   
  161                       ValueStack stack = ac.getValueStack();
  162                       setParameters(action, stack, parameters);
  163                   } finally {
  164                   	ReflectionContextState.setCreatingNullObjects(contextMap, false);
  165                   	ReflectionContextState.setDenyMethodExecution(contextMap, false);
  166                   	ReflectionContextState.setReportingConversionErrors(contextMap, false);
  167                   }
  168               }
  169           }
  170           return invocation.invoke();
  171       }
  172   
  173       /**
  174        * Gets the parameter map to apply from the context
  175        * @param ac The action context
  176        * @return The parameter map to apply
  177        */
  178       protected Map retrieveParametersFromContext(ActionContext ac) {
  179           return ac.getParameters();
  180       }
  181   
  182       protected void setParameters(Object action, ValueStack stack, final Map parameters) {
  183           ParameterNameAware parameterNameAware = (action instanceof ParameterNameAware)
  184                   ? (ParameterNameAware) action : null;
  185   
  186           Map params = null;
  187           if( ordered ) {
  188               params = new TreeMap(getOrderedComparator());
  189               params.putAll(parameters);
  190           } else {
  191               params = new TreeMap(parameters); 
  192           }
  193           
  194           for (Iterator iterator = params.entrySet().iterator(); iterator.hasNext();) {
  195               Map.Entry entry = (Map.Entry) iterator.next();
  196               String name = entry.getKey().toString();
  197   
  198               boolean acceptableName = acceptableName(name)
  199                       && (parameterNameAware == null
  200                       || parameterNameAware.acceptableParameterName(name));
  201   
  202               if (acceptableName) {
  203                   Object value = entry.getValue();
  204                   try {
  205                       stack.setValue(name, value);
  206                   } catch (RuntimeException e) {
  207                       if (devMode) {
  208                           String developerNotification = LocalizedTextUtil.findText(ParametersInterceptor.class, "devmode.notification", ActionContext.getContext().getLocale(), "Developer Notification:\n{0}", new Object[]{
  209                                   e.getMessage()
  210                           });
  211                           LOG.error(developerNotification);
  212                           if (action instanceof ValidationAware) {
  213                               ((ValidationAware) action).addActionMessage(developerNotification);
  214                           }
  215                       } else {
  216                           LOG.error("ParametersInterceptor - [setParameters]: Unexpected Exception caught setting '"+name+"' on '"+action.getClass()+": " + e.getMessage());
  217                       }
  218                   }
  219               }
  220           }
  221       }
  222   
  223       /**
  224        * Gets an instance of the comparator to use for the ordered sorting.  Override this
  225        * method to customize the ordering of the parameters as they are set to the 
  226        * action.
  227        * 
  228        * @return A comparator to sort the parameters
  229        */
  230       protected Comparator getOrderedComparator() {
  231           return rbCollator;
  232       }
  233   
  234       private String getParameterLogMap(Map parameters) {
  235           if (parameters == null) {
  236               return "NONE";
  237           }
  238   
  239           StringBuffer logEntry = new StringBuffer();
  240           for (Iterator paramIter = parameters.entrySet().iterator(); paramIter.hasNext();) {
  241               Map.Entry entry = (Map.Entry) paramIter.next();
  242               logEntry.append(String.valueOf(entry.getKey()));
  243               logEntry.append(" => ");
  244               if (entry.getValue() instanceof Object[]) {
  245                   Object[] valueArray = (Object[]) entry.getValue();
  246                   logEntry.append("[ ");
  247                   for (int indexA = 0; indexA < (valueArray.length - 1); indexA++) {
  248                       Object valueAtIndex = valueArray[indexA];
  249                       logEntry.append(valueAtIndex);
  250                       logEntry.append(String.valueOf(valueAtIndex));
  251                       logEntry.append(", ");
  252                   }
  253                   logEntry.append(String.valueOf(valueArray[valueArray.length - 1]));
  254                   logEntry.append(" ] ");
  255               } else {
  256                   logEntry.append(String.valueOf(entry.getValue()));
  257               }
  258           }
  259   
  260           return logEntry.toString();
  261       }
  262   
  263   
  264       protected boolean acceptableName(String name) {
  265           if (name.indexOf('=') != -1 || name.indexOf(',') != -1 || name.indexOf('#') != -1
  266                   || name.indexOf(':') != -1 || isExcluded(name)) {
  267               return false;
  268           } else {
  269               return true;
  270           }
  271       }
  272       
  273       protected boolean isExcluded(String paramName) {
  274           if (!this.excludeParams.isEmpty()) {
  275               for (Pattern pattern : excludeParams) {
  276                   Matcher matcher = pattern.matcher(paramName);
  277                   if (matcher.matches()) {
  278                       return true;
  279                   }
  280               }
  281           }
  282           return false;
  283       }
  284   
  285       /**
  286        * Whether to order the parameters or not
  287        * 
  288        * @return True to order
  289        */
  290       public boolean isOrdered() {
  291           return ordered;
  292       }
  293   
  294       /**
  295        * Set whether to order the parameters by object depth or not
  296        * 
  297        * @param ordered True to order them
  298        */
  299       public void setOrdered(boolean ordered) {
  300           this.ordered = ordered;
  301       }
  302       
  303       /**
  304        * Gets a set of regular expressions of parameters to remove
  305        * from the parameter map
  306        * 
  307        * @return A set of compiled regular expression patterns
  308        */
  309       protected Set getExcludeParamsSet() {
  310           return excludeParams;
  311       }
  312   
  313       /**
  314        * Sets a comma-delimited list of regular expressions to match 
  315        * parameters that should be removed from the parameter map.
  316        * 
  317        * @param commaDelim A comma-delimited list of regular expressions
  318        */
  319       public void setExcludeParams(String commaDelim) {
  320           Collection<String> excludePatterns = asCollection(commaDelim);
  321           if (excludePatterns != null) {
  322               excludeParams = new HashSet<Pattern>();
  323               for (String pattern : excludePatterns) {
  324                   excludeParams.add(Pattern.compile(pattern));
  325               }
  326           }
  327       }
  328   
  329       /**
  330        * Return a collection from the comma delimited String.
  331        *
  332        * @param commaDelim the comma delimited String.
  333        * @return A collection from the comma delimited String. Returns <tt>null</tt> if the string is empty.
  334        */
  335       private Collection asCollection(String commaDelim) {
  336           if (commaDelim == null || commaDelim.trim().length() == 0) {
  337               return null;
  338           }
  339           return TextParseUtil.commaDelimitedStringToSet(commaDelim);
  340       }
  341   
  342   }

Save This Page
Home » xwork-2.1.1-src » com.opensymphony.xwork2.interceptor » [javadoc | source]