Save This Page
Home » xwork-2.1.1-src » com.opensymphony.xwork2.ognl » [javadoc | source]
    1   /*
    2    * Copyright (c) 2002-2006 by OpenSymphony
    3    * All rights reserved.
    4    */
    5   package com.opensymphony.xwork2.ognl;
    6   
    7   import java.io.Serializable;
    8   import java.util.HashMap;
    9   import java.util.Map;
   10   import java.util.Set;
   11   import java.util.LinkedHashSet;
   12   import java.beans.IntrospectionException;
   13   import java.beans.PropertyDescriptor;
   14   import java.lang.reflect.Method;
   15   
   16   import ognl.Ognl;
   17   import ognl.OgnlContext;
   18   import ognl.OgnlException;
   19   import ognl.PropertyAccessor;
   20   
   21   import com.opensymphony.xwork2.ActionContext;
   22   import com.opensymphony.xwork2.TextProvider;
   23   import com.opensymphony.xwork2.XWorkException;
   24   import com.opensymphony.xwork2.ActionSupport;
   25   import com.opensymphony.xwork2.conversion.impl.XWorkConverter;
   26   import com.opensymphony.xwork2.inject.Container;
   27   import com.opensymphony.xwork2.inject.Inject;
   28   import com.opensymphony.xwork2.ognl.accessor.CompoundRootAccessor;
   29   import com.opensymphony.xwork2.util.CompoundRoot;
   30   import com.opensymphony.xwork2.util.ValueStack;
   31   import com.opensymphony.xwork2.util.logging.Logger;
   32   import com.opensymphony.xwork2.util.logging.LoggerFactory;
   33   import com.opensymphony.xwork2.util.logging.LoggerUtils;
   34   import com.opensymphony.xwork2.util.reflection.ReflectionContextState;
   35   
   36   /**
   37    * Ognl implementation of a value stack that allows for dynamic Ognl expressions to be evaluated against it. When
   38    * evaluating an expression, the stack will be searched down the stack, from the latest objects pushed in to the
   39    * earliest, looking for a bean with a getter or setter for the given property or a method of the given name (depending
   40    * on the expression being evaluated).
   41    *
   42    * @author Patrick Lightbody
   43    * @author tm_jee
   44    * @version $Date: 2007-10-15 19:04:43 +0200 (Mon, 15 Oct 2007) $ $Id: OgnlValueStack.java 1647 2007-10-15 17:04:43Z rainerh $
   45    */
   46   public class OgnlValueStack implements Serializable, ValueStack {
   47   
   48       private static final long serialVersionUID = 370737852934925530L;
   49   
   50       private static Logger LOG = LoggerFactory.getLogger(OgnlValueStack.class);
   51       private boolean devMode;
   52   
   53       public static void link(Map context, Class clazz, String name) {
   54           context.put("__link", new Object[]{clazz, name});
   55       }
   56   
   57   
   58       CompoundRoot root;
   59       transient Map context;
   60       Class defaultType;
   61       Map overrides;
   62       transient OgnlUtil ognlUtil;
   63   
   64       protected OgnlValueStack(XWorkConverter xworkConverter, CompoundRootAccessor accessor, TextProvider prov, boolean allowStaticAccess) {
   65           setRoot(xworkConverter, accessor, new CompoundRoot(), allowStaticAccess);
   66           push(prov);
   67       }
   68   
   69   
   70       protected OgnlValueStack(ValueStack vs, XWorkConverter xworkConverter, CompoundRootAccessor accessor, boolean allowStaticAccess) {
   71           setRoot(xworkConverter, accessor, new CompoundRoot(vs.getRoot()), allowStaticAccess);
   72       }
   73   
   74       @Inject
   75       public void setOgnlUtil(OgnlUtil ognlUtil) {
   76           this.ognlUtil = ognlUtil;
   77       }
   78   
   79       protected void setRoot(XWorkConverter xworkConverter,
   80                              CompoundRootAccessor accessor, CompoundRoot compoundRoot, boolean allowStaticMethodAccess) {
   81           this.root = compoundRoot;
   82           this.context = Ognl.createDefaultContext(this.root, accessor, new OgnlTypeConverterWrapper(xworkConverter),
   83                   new StaticMemberAccess(allowStaticMethodAccess));
   84           context.put(VALUE_STACK, this);
   85           Ognl.setClassResolver(context, accessor);
   86           ((OgnlContext) context).setTraceEvaluations(false);
   87           ((OgnlContext) context).setKeepLastEvaluation(false);
   88       }
   89   
   90       @Inject("devMode")
   91       public void setDevMode(String mode) {
   92           devMode = "true".equalsIgnoreCase(mode);
   93       }
   94   
   95       /* (non-Javadoc)
   96        * @see com.opensymphony.xwork2.util.ValueStack#getContext()
   97        */
   98       public Map getContext() {
   99           return context;
  100       }
  101   
  102       /* (non-Javadoc)
  103        * @see com.opensymphony.xwork2.util.ValueStack#setDefaultType(java.lang.Class)
  104        */
  105       public void setDefaultType(Class defaultType) {
  106           this.defaultType = defaultType;
  107       }
  108   
  109       /* (non-Javadoc)
  110        * @see com.opensymphony.xwork2.util.ValueStack#setExprOverrides(java.util.Map)
  111        */
  112       public void setExprOverrides(Map overrides) {
  113           if (this.overrides == null) {
  114               this.overrides = overrides;
  115           } else {
  116               this.overrides.putAll(overrides);
  117           }
  118       }
  119   
  120       /* (non-Javadoc)
  121       * @see com.opensymphony.xwork2.util.ValueStack#getExprOverrides()
  122       */
  123       public Map getExprOverrides() {
  124           return this.overrides;
  125       }
  126   
  127       /* (non-Javadoc)
  128        * @see com.opensymphony.xwork2.util.ValueStack#getRoot()
  129        */
  130       public CompoundRoot getRoot() {
  131           return root;
  132       }
  133   
  134       /* (non-Javadoc)
  135        * @see com.opensymphony.xwork2.util.ValueStack#setValue(java.lang.String, java.lang.Object)
  136        */
  137       public void setValue(String expr, Object value) {
  138           setValue(expr, value, devMode);
  139       }
  140   
  141       /* (non-Javadoc)
  142        * @see com.opensymphony.xwork2.util.ValueStack#setValue(java.lang.String, java.lang.Object, boolean)
  143        */
  144       public void setValue(String expr, Object value, boolean throwExceptionOnFailure) {
  145           Map context = getContext();
  146   
  147           try {
  148               context.put(XWorkConverter.CONVERSION_PROPERTY_FULLNAME, expr);
  149               context.put(REPORT_ERRORS_ON_NO_PROP, (throwExceptionOnFailure) ? Boolean.TRUE : Boolean.FALSE);
  150               ognlUtil.setValue(expr, context, root, value);
  151           } catch (OgnlException e) {
  152               if (throwExceptionOnFailure) {
  153                   e.printStackTrace(System.out);
  154                   System.out.println("expr: " + expr + " val: " + value + " context: " + context + " root:" + root + " value: " + value);
  155                   String msg = "Error setting expression '" + expr + "' with value '" + value + "'";
  156                   throw new XWorkException(msg, e);
  157               } else {
  158                   if (LOG.isDebugEnabled()) {
  159                       LOG.debug("Error setting value", e);
  160                   }
  161               }
  162           } catch (RuntimeException re) { //XW-281
  163               if (throwExceptionOnFailure) {
  164                   StringBuffer msg = new StringBuffer();
  165                   msg.append("Error setting expression '");
  166                   msg.append(expr);
  167                   msg.append("' with value ");
  168   
  169                   if (value instanceof Object[]) {
  170                       Object[] valueArray = (Object[]) value;
  171                       msg.append("[");
  172                       for (int index = 0; index < valueArray.length; index++) {
  173                           msg.append("'");
  174                           msg.append(valueArray[index]);
  175                           msg.append("'");
  176   
  177                           if (index < (valueArray.length + 1))
  178                               msg.append(", ");
  179                       }
  180                       msg.append("]");
  181                   } else {
  182                       msg.append("'");
  183                       msg.append(value);
  184                       msg.append("'");
  185                   }
  186   
  187                   throw new XWorkException(msg.toString(), re);
  188               } else {
  189                   if (LOG.isDebugEnabled()) {
  190                       LOG.debug("Error setting value", re);
  191                   }
  192               }
  193           } finally {
  194               ReflectionContextState.clear(context);
  195               context.remove(XWorkConverter.CONVERSION_PROPERTY_FULLNAME);
  196               context.remove(REPORT_ERRORS_ON_NO_PROP);
  197           }
  198       }
  199   
  200       /* (non-Javadoc)
  201        * @see com.opensymphony.xwork2.util.ValueStack#findString(java.lang.String)
  202        */
  203       public String findString(String expr) {
  204           return (String) findValue(expr, String.class);
  205       }
  206   
  207       /* (non-Javadoc)
  208        * @see com.opensymphony.xwork2.util.ValueStack#findValue(java.lang.String)
  209        */
  210       public Object findValue(String expr) {
  211           try {
  212               if (expr == null) {
  213                   return null;
  214               }
  215   
  216               if ((overrides != null) && overrides.containsKey(expr)) {
  217                   expr = (String) overrides.get(expr);
  218               }
  219   
  220               if (defaultType != null) {
  221                   return findValue(expr, defaultType);
  222               }
  223   
  224               Object value = ognlUtil.getValue(expr, context, root);
  225               if (value != null) {
  226                   return value;
  227               } else {
  228                   checkForInvalidProperties(expr);
  229                   return findInContext(expr);
  230               }
  231           } catch (OgnlException e) {
  232               checkForInvalidProperties(expr);
  233               return findInContext(expr);
  234           } catch (Exception e) {
  235               logLookupFailure(expr, e);
  236   
  237               return findInContext(expr);
  238           } finally {
  239               ReflectionContextState.clear(context);
  240           }
  241       }
  242   
  243       /* (non-Javadoc)
  244        * @see com.opensymphony.xwork2.util.ValueStack#findValue(java.lang.String, java.lang.Class)
  245        */
  246       public Object findValue(String expr, Class asType) {
  247           try {
  248               if (expr == null) {
  249                   return null;
  250               }
  251   
  252               if ((overrides != null) && overrides.containsKey(expr)) {
  253                   expr = (String) overrides.get(expr);
  254               }
  255   
  256               Object value = ognlUtil.getValue(expr, context, root, asType);
  257               if (value != null) {
  258                   return value;
  259               } else {
  260                   return findInContext(expr);
  261               }
  262           } catch (OgnlException e) {
  263               return findInContext(expr);
  264           } catch (Exception e) {
  265               logLookupFailure(expr, e);
  266   
  267               return findInContext(expr);
  268           } finally {
  269               ReflectionContextState.clear(context);
  270           }
  271       }
  272   
  273       private Object findInContext(String name) {
  274           return getContext().get(name);
  275       }
  276   
  277   
  278        /**
  279        * This method looks for matching methods/properties in an action to warn the user if
  280        * they specified a property that doesn't exist.
  281        * @param expr the property expression
  282        */
  283       private void checkForInvalidProperties(String expr) {
  284           if (expr.contains("(") && expr.contains(")")) {
  285               LOG.warn("Could not find method [" + expr + "]");
  286           } else if (findInContext(expr) == null) {
  287               // find objects with Action in them and inspect matching getters
  288               Set availableProperties = new LinkedHashSet();
  289               for (Object o : root) {
  290                   if (o instanceof ActionSupport || o.getClass().getSimpleName().endsWith("Action")) {
  291                       try {
  292                           findAvailableProperties(o.getClass(), expr, availableProperties, null);
  293                       } catch (IntrospectionException ise) {
  294                           // ignore
  295                       }
  296                   }
  297               }
  298               if (!availableProperties.contains(expr)) {
  299                   LOG.warn("Could not find property [" + expr + "]");
  300               }
  301           }
  302       }
  303   
  304       /**
  305        * Look for available properties on an existing class.
  306        * @param c the class to search on
  307        * @param expr the property expression
  308        * @param availableProperties a set of properties found
  309        * @param parent a parent property
  310        * @throws IntrospectionException when Ognl can't get property descriptors
  311        */
  312       private void findAvailableProperties(Class c, String expr, Set availableProperties, String parent) throws IntrospectionException {
  313           PropertyDescriptor[] descriptors = ognlUtil.getPropertyDescriptors(c);
  314           for (PropertyDescriptor pd : descriptors) {
  315               String name = pd.getDisplayName();
  316               if (parent != null && expr.indexOf(".") > -1) {
  317                   name = expr.substring(0, expr.indexOf(".") + 1) + name;
  318               }
  319               if (expr.startsWith(name)) {
  320                  availableProperties.add((parent != null) ? parent + "." + name : name);
  321                   if (expr.equals(name)) break; // no need to go any further
  322                   if (expr.indexOf(".") > -1) {
  323                       String property = expr.substring(expr.indexOf(".") + 1);
  324                       // if there is a nested property (indicated by a dot), chop it off so we can look for method name
  325                       String rawProperty = (property.indexOf(".") > -1) ? property.substring(0, property.indexOf(".")) : property;
  326                       String methodToLookFor = "get" + rawProperty.substring(0, 1).toUpperCase() + rawProperty.substring(1);
  327                       Method[] methods = pd.getPropertyType().getDeclaredMethods();
  328                       for (Method method : methods) {
  329                           if (method.getName().equals(methodToLookFor)) {
  330                               availableProperties.add(name + "." + rawProperty);
  331                               Class returnType = method.getReturnType();
  332                               findAvailableProperties(returnType, property, availableProperties, name);
  333                           }
  334                       }
  335   
  336                   }
  337               }
  338           }
  339       }
  340   
  341       /**
  342        * Log a failed lookup, being more verbose when devMode=true.
  343        *
  344        * @param expr The failed expression
  345        * @param e    The thrown exception.
  346        */
  347       private void logLookupFailure(String expr, Exception e) {
  348           String msg = LoggerUtils.format("Caught an exception while evaluating expression '#0' against value stack", expr);
  349           if (devMode && LOG.isWarnEnabled()) {
  350               LOG.warn(msg, e);
  351               LOG.warn("NOTE: Previous warning message was issued due to devMode set to true.");
  352           } else if (LOG.isDebugEnabled()) {
  353               LOG.debug(msg, e);
  354           }
  355       }
  356   
  357       /* (non-Javadoc)
  358        * @see com.opensymphony.xwork2.util.ValueStack#peek()
  359        */
  360       public Object peek() {
  361           return root.peek();
  362       }
  363   
  364       /* (non-Javadoc)
  365        * @see com.opensymphony.xwork2.util.ValueStack#pop()
  366        */
  367       public Object pop() {
  368           return root.pop();
  369       }
  370   
  371       /* (non-Javadoc)
  372        * @see com.opensymphony.xwork2.util.ValueStack#push(java.lang.Object)
  373        */
  374       public void push(Object o) {
  375           root.push(o);
  376       }
  377   
  378       /* (non-Javadoc)
  379       * @see com.opensymphony.xwork2.util.ValueStack#set(java.lang.String, java.lang.Object)
  380       */
  381       public void set(String key, Object o) {
  382           //set basically is backed by a Map
  383           //pushed on the stack with a key
  384           //being put on the map and the
  385           //Object being the value
  386   
  387           Map setMap = null;
  388   
  389           //check if this is a Map
  390           //put on the stack  for setting
  391           //if so just use the old map (reduces waste)
  392           Object topObj = peek();
  393           if (topObj instanceof Map
  394                   && ((Map) topObj).get(MAP_IDENTIFIER_KEY) != null) {
  395   
  396               setMap = (Map) topObj;
  397           } else {
  398               setMap = new HashMap();
  399               //the map identifier key ensures
  400               //that this map was put there
  401               //for set purposes and not by a user
  402               //whose data we don't want to touch
  403               setMap.put(MAP_IDENTIFIER_KEY, "");
  404               push(setMap);
  405           }
  406           setMap.put(key, o);
  407   
  408       }
  409   
  410   
  411       private static final String MAP_IDENTIFIER_KEY = "com.opensymphony.xwork2.util.OgnlValueStack.MAP_IDENTIFIER_KEY";
  412   
  413       /* (non-Javadoc)
  414       * @see com.opensymphony.xwork2.util.ValueStack#size()
  415       */
  416       public int size() {
  417           return root.size();
  418       }
  419   
  420       private Object readResolve() {
  421           // TODO: this should be done better
  422           ActionContext ac = ActionContext.getContext();
  423           Container cont = ac.getContainer();
  424           XWorkConverter xworkConverter = cont.getInstance(XWorkConverter.class);
  425           CompoundRootAccessor accessor = (CompoundRootAccessor) cont.getInstance(PropertyAccessor.class, CompoundRoot.class.getName());
  426           TextProvider prov = cont.getInstance(TextProvider.class, "system");
  427           boolean allow = "true".equals(cont.getInstance(String.class, "allowStaticMethodAccess"));
  428           OgnlValueStack aStack = new OgnlValueStack(xworkConverter, accessor, prov, allow);
  429           aStack.setOgnlUtil(cont.getInstance(OgnlUtil.class));
  430           aStack.setRoot(xworkConverter, accessor, this.root, allow);
  431   
  432           return aStack;
  433       }
  434   
  435   
  436   }

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