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