001: /*
002: * Copyright (c) 2002-2006 by OpenSymphony
003: * All rights reserved.
004: */
005: package com.opensymphony.xwork.util;
006:
007: import com.opensymphony.xwork.ActionContext;
008: import com.opensymphony.xwork.DefaultTextProvider;
009: import com.opensymphony.xwork.XworkException;
010: import ognl.*;
011: import org.apache.commons.logging.Log;
012: import org.apache.commons.logging.LogFactory;
013:
014: import java.io.Serializable;
015: import java.util.*;
016:
017: /**
018: * OgnlValueStack allows multiple beans to be pushed in and dynamic Ognl expressions to be evaluated against it. When
019: * evaluating an expression, the stack will be searched down the stack, from the latest objects pushed in to the
020: * earliest, looking for a bean with a getter or setter for the given property or a method of the given name (depending
021: * on the expression being evaluated).
022: *
023: * @author Patrick Lightbody
024: */
025: public class OgnlValueStack implements Serializable {
026:
027: private static final long serialVersionUID = -3444677371593148954L;
028:
029: public static final String VALUE_STACK = "com.opensymphony.xwork.util.OgnlValueStack.ValueStack";
030: public static final String REPORT_ERRORS_ON_NO_PROP = "com.opensymphony.xwork.util.OgnlValueStack.ReportErrorsOnNoProp";
031: private static CompoundRootAccessor accessor;
032: private static Log LOG = LogFactory.getLog(OgnlValueStack.class);
033:
034: static {
035: reset();
036: }
037:
038: public static void reset() {
039: accessor = new CompoundRootAccessor();
040: OgnlRuntime.setPropertyAccessor(CompoundRoot.class, accessor);
041: OgnlRuntime.setPropertyAccessor(Object.class,
042: new ObjectAccessor());
043: OgnlRuntime.setPropertyAccessor(Iterator.class,
044: new XWorkIteratorPropertyAccessor());
045: OgnlRuntime.setPropertyAccessor(Enumeration.class,
046: new XWorkEnumerationAcccessor());
047: OgnlRuntime.setPropertyAccessor(List.class,
048: new XWorkListPropertyAccessor());
049: OgnlRuntime.setPropertyAccessor(Map.class,
050: new XWorkMapPropertyAccessor());
051: OgnlRuntime.setPropertyAccessor(Collection.class,
052: new XWorkCollectionPropertyAccessor());
053: OgnlRuntime.setPropertyAccessor(Set.class,
054: new XWorkCollectionPropertyAccessor());
055: OgnlRuntime.setPropertyAccessor(ObjectProxy.class,
056: new ObjectProxyPropertyAccessor());
057: OgnlRuntime.setMethodAccessor(Object.class,
058: new XWorkMethodAccessor());
059: OgnlRuntime.setMethodAccessor(CompoundRoot.class, accessor);
060: OgnlRuntime.setNullHandler(Object.class,
061: new InstantiatingNullHandler());
062: }
063:
064: public static class ObjectAccessor extends ObjectPropertyAccessor {
065: public Object getProperty(Map map, Object o, Object o1)
066: throws OgnlException {
067: Object obj = super .getProperty(map, o, o1);
068: link(map, o.getClass(), (String) o1);
069:
070: map.put(XWorkConverter.LAST_BEAN_CLASS_ACCESSED, o
071: .getClass());
072: map.put(XWorkConverter.LAST_BEAN_PROPERTY_ACCESSED, o1
073: .toString());
074: OgnlContextState.updateCurrentPropertyPath(map, o1);
075: return obj;
076: }
077:
078: public void setProperty(Map map, Object o, Object o1, Object o2)
079: throws OgnlException {
080: super .setProperty(map, o, o1, o2);
081: }
082: }
083:
084: public static void link(Map context, Class clazz, String name) {
085: context.put("__link", new Object[] { clazz, name });
086: }
087:
088: CompoundRoot root;
089: transient Map context;
090: Class defaultType;
091: Map overrides;
092:
093: public OgnlValueStack() {
094: setRoot(new CompoundRoot());
095: push(DefaultTextProvider.INSTANCE);
096: }
097:
098: public OgnlValueStack(OgnlValueStack vs) {
099: setRoot(new CompoundRoot(vs.getRoot()));
100: }
101:
102: public static CompoundRootAccessor getAccessor() {
103: return accessor;
104: }
105:
106: public Map getContext() {
107: return context;
108: }
109:
110: /**
111: * Sets the default type to convert to if no type is provided when getting a value.
112: *
113: * @param defaultType
114: */
115: public void setDefaultType(Class defaultType) {
116: this .defaultType = defaultType;
117: }
118:
119: public void setExprOverrides(Map overrides) {
120: if (this .overrides == null) {
121: this .overrides = overrides;
122: } else {
123: this .overrides.putAll(overrides);
124: }
125: }
126:
127: /**
128: * Get the CompoundRoot which holds the objects pushed onto the stack
129: */
130: public CompoundRoot getRoot() {
131: return root;
132: }
133:
134: /**
135: * Determine whether devMode is enabled.
136: * @return true if devMode was enabled, false otherwise.
137: */
138: public boolean isDevModeEnabled() {
139: Boolean devMode = (Boolean) context.get(ActionContext.DEV_MODE);
140: if (devMode != null) {
141: return devMode.booleanValue();
142: } else {
143: return false;
144: }
145: }
146:
147: /**
148: * Attempts to set a property on a bean in the stack with the given expression using the default search order.
149: *
150: * @param expr the expression defining the path to the property to be set.
151: * @param value the value to be set into the neamed property
152: */
153: public void setValue(String expr, Object value) {
154: setValue(expr, value, isDevModeEnabled());
155: }
156:
157: /**
158: * Attempts to set a property on a bean in the stack with the given expression using the default search order.
159: *
160: * @param expr the expression defining the path to the property to be set.
161: * @param value the value to be set into the neamed property
162: * @param throwExceptionOnFailure a flag to tell whether an exception should be thrown if there is no property with
163: * the given name.
164: */
165: public void setValue(String expr, Object value,
166: boolean throwExceptionOnFailure) {
167: Map context = getContext();
168:
169: try {
170: context.put(XWorkConverter.CONVERSION_PROPERTY_FULLNAME,
171: expr);
172: context.put(REPORT_ERRORS_ON_NO_PROP,
173: (throwExceptionOnFailure) ? Boolean.TRUE
174: : Boolean.FALSE);
175: OgnlUtil.setValue(expr, context, root, value);
176: } catch (OgnlException e) {
177: if (throwExceptionOnFailure) {
178: String msg = "Error setting expr '" + expr
179: + "' with value '" + value + "'";
180: LOG.error(msg, e);
181: throw new XworkException(msg, e);
182: } else {
183: if (LOG.isDebugEnabled()) {
184: LOG.debug("Error setting value", e);
185: }
186: }
187: } catch (XworkException re) { //XW-281
188: if (throwExceptionOnFailure) {
189: String msg = "Error setting expr '" + expr
190: + "' with value '" + value + "'";
191: LOG.error(msg, re);
192: throw re;
193: } else {
194: if (LOG.isDebugEnabled()) {
195: LOG.debug("Error setting value", re);
196: }
197: }
198: } catch (RuntimeException re) { //XW-281
199: if (throwExceptionOnFailure) {
200: String msg = "Error setting expr '" + expr
201: + "' with value '" + value + "'";
202: LOG.error(msg, re);
203: throw new XworkException(msg, re);
204: } else {
205: if (LOG.isDebugEnabled()) {
206: LOG.debug("Error setting value", re);
207: }
208: }
209: } finally {
210: OgnlContextState.clear(context);
211: context.remove(XWorkConverter.CONVERSION_PROPERTY_FULLNAME);
212: context.remove(REPORT_ERRORS_ON_NO_PROP);
213: }
214: }
215:
216: public String findString(String expr) {
217: return (String) findValue(expr, String.class);
218: }
219:
220: /**
221: * Find a value by evaluating the given expression against the stack in the default search order.
222: *
223: * @param expr the expression giving the path of properties to navigate to find the property value to return
224: * @return the result of evaluating the expression
225: */
226: public Object findValue(String expr) {
227: try {
228: if (expr == null) {
229: return null;
230: }
231:
232: if ((overrides != null) && overrides.containsKey(expr)) {
233: expr = (String) overrides.get(expr);
234: }
235:
236: if (defaultType != null) {
237: return findValue(expr, defaultType);
238: }
239:
240: Object value = OgnlUtil.getValue(expr, context, root);
241: if (value != null) {
242: return value;
243: } else {
244: return findInContext(expr);
245: }
246: } catch (OgnlException e) {
247: return findInContext(expr);
248: } catch (Exception e) {
249: logLookupFailure(expr, e);
250:
251: return findInContext(expr);
252: } finally {
253: OgnlContextState.clear(context);
254: }
255: }
256:
257: /**
258: * Find a value by evaluating the given expression against the stack in the default search order.
259: *
260: * @param expr the expression giving the path of properties to navigate to find the property value to return
261: * @param asType the type to convert the return value to
262: * @return the result of evaluating the expression
263: */
264: public Object findValue(String expr, Class asType) {
265: try {
266: if (expr == null) {
267: return null;
268: }
269:
270: if ((overrides != null) && overrides.containsKey(expr)) {
271: expr = (String) overrides.get(expr);
272: }
273:
274: Object value = OgnlUtil.getValue(expr, context, root,
275: asType);
276: if (value != null) {
277: return value;
278: } else {
279: return findInContext(expr);
280: }
281: } catch (OgnlException e) {
282: return findInContext(expr);
283: } catch (Exception e) {
284: logLookupFailure(expr, e);
285:
286: return findInContext(expr);
287: } finally {
288: OgnlContextState.clear(context);
289: }
290: }
291:
292: private Object findInContext(String name) {
293: return getContext().get(name);
294: }
295:
296: /**
297: * Log a failed lookup, being more verbose when devMode=true.
298: *
299: * @param expr The failed expression
300: * @param e The thrown exception.
301: */
302: private void logLookupFailure(String expr, Exception e) {
303: StringBuffer msg = new StringBuffer();
304: msg.append("Caught an exception while evaluating expression '")
305: .append(expr).append("' against value stack");
306: if (isDevModeEnabled() && LOG.isWarnEnabled()) {
307: LOG.warn(msg, e);
308: LOG
309: .warn("NOTE: Previous warning message was issued due to devMode set to true.");
310: } else if (LOG.isDebugEnabled()) {
311: LOG.debug(msg, e);
312: }
313: }
314:
315: /**
316: * Get the object on the top of the stack without changing the stack.
317: *
318: * @see CompoundRoot#peek()
319: */
320: public Object peek() {
321: return root.peek();
322: }
323:
324: /**
325: * Get the object on the top of the stack and remove it from the stack.
326: *
327: * @return the object on the top of the stack
328: * @see CompoundRoot#pop()
329: */
330: public Object pop() {
331: return root.pop();
332: }
333:
334: /**
335: * Put this object onto the top of the stack
336: *
337: * @param o the object to be pushed onto the stack
338: * @see CompoundRoot#push(Object)
339: */
340: public void push(Object o) {
341: root.push(o);
342: }
343:
344: /**
345: * Get the number of objects in the stack
346: * s
347: *
348: * @return the number of objects in the stack
349: */
350: public int size() {
351: return root.size();
352: }
353:
354: private void setRoot(CompoundRoot compoundRoot) {
355: this .root = compoundRoot;
356: this .context = Ognl.createDefaultContext(this .root, accessor,
357: XWorkConverter.getInstance());
358: context.put(VALUE_STACK, this );
359: Ognl.setClassResolver(context, accessor);
360: ((OgnlContext) context).setTraceEvaluations(false);
361: ((OgnlContext) context).setKeepLastEvaluation(false);
362: }
363:
364: private Object readResolve() {
365: OgnlValueStack aStack = new OgnlValueStack();
366: aStack.setRoot(this.root);
367:
368: return aStack;
369: }
370: }
|