001: /*
002: * Copyright (c) 2002-2006 by OpenSymphony
003: * All rights reserved.
004: */
005: package com.opensymphony.xwork.util;
006:
007: import ognl.Ognl;
008: import ognl.OgnlContext;
009: import ognl.OgnlException;
010: import ognl.OgnlRuntime;
011: import org.apache.commons.logging.Log;
012: import org.apache.commons.logging.LogFactory;
013:
014: import java.beans.BeanInfo;
015: import java.beans.IntrospectionException;
016: import java.beans.Introspector;
017: import java.beans.PropertyDescriptor;
018: import java.lang.reflect.Method;
019: import java.util.Collection;
020: import java.util.HashMap;
021: import java.util.Iterator;
022: import java.util.Map;
023:
024: import com.opensymphony.xwork.XworkException;
025:
026: /**
027: * Utility class that provides common access to the <a href="www.ognl.org">Ognl</a> APIs for
028: * setting and getting properties from objects (usually Actions).
029: *
030: * @author Jason Carreira
031: * @author tmjee
032: * @version $Date: 2007-07-15 05:56:02 +0200 (Sun, 15 Jul 2007) $ $Id: OgnlUtil.java 1542 2007-07-15 03:56:02Z tm_jee $
033: */
034: public class OgnlUtil {
035:
036: private static final Log log = LogFactory.getLog(OgnlUtil.class);
037: private static HashMap expressions = new HashMap();
038: private static HashMap beanInfoCache = new HashMap();
039:
040: /**
041: * Sets the object's properties using the default type converter, defaulting to not throw
042: * exceptions for problems setting the properties.
043: *
044: * @param props the properties being set
045: * @param o the object
046: * @param context the action context
047: */
048: public static void setProperties(Map props, Object o, Map context) {
049: setProperties(props, o, context, false);
050: }
051:
052: /**
053: * Sets the object's properties using the default type converter.
054: *
055: * @param props the properties being set
056: * @param o the object
057: * @param context the action context
058: * @param throwPropertyExceptions boolean which tells whether it should throw exceptions for
059: * problems setting the properties
060: */
061: public static void setProperties(Map props, Object o, Map context,
062: boolean throwPropertyExceptions) {
063: if (props == null) {
064: return;
065: }
066:
067: Ognl.setTypeConverter(context, XWorkConverter.getInstance());
068:
069: Object oldRoot = Ognl.getRoot(context);
070: Ognl.setRoot(context, o);
071:
072: for (Iterator iterator = props.entrySet().iterator(); iterator
073: .hasNext();) {
074: Map.Entry entry = (Map.Entry) iterator.next();
075: String expression = (String) entry.getKey();
076:
077: internalSetProperty(expression, entry.getValue(), o,
078: context, throwPropertyExceptions);
079: }
080:
081: Ognl.setRoot(context, oldRoot);
082: }
083:
084: /**
085: * Sets the properties on the object using the default context, defaulting to not throwing
086: * exceptions for problems setting the properties.
087: *
088: * @param properties
089: * @param o
090: */
091: public static void setProperties(Map properties, Object o) {
092: setProperties(properties, o, false);
093: }
094:
095: /**
096: * Sets the properties on the object using the default context.
097: *
098: * @param properties the property map to set on the object
099: * @param o the object to set the properties into
100: * @param throwPropertyExceptions boolean which tells whether it should throw exceptions for
101: * problems setting the properties
102: */
103: public static void setProperties(Map properties, Object o,
104: boolean throwPropertyExceptions) {
105: Map context = Ognl.createDefaultContext(o);
106: setProperties(properties, o, context, throwPropertyExceptions);
107: }
108:
109: /**
110: * Sets the named property to the supplied value on the Object, defaults to not throwing
111: * property exceptions.
112: *
113: * @param name the name of the property to be set
114: * @param value the value to set into the named property
115: * @param o the object upon which to set the property
116: * @param context the context which may include the TypeConverter
117: */
118: public static void setProperty(String name, Object value, Object o,
119: Map context) {
120: setProperty(name, value, o, context, false);
121: }
122:
123: /**
124: * Sets the named property to the supplied value on the Object.
125: *
126: * @param name the name of the property to be set
127: * @param value the value to set into the named property
128: * @param o the object upon which to set the property
129: * @param context the context which may include the TypeConverter
130: * @param throwPropertyExceptions boolean which tells whether it should throw exceptions for
131: * problems setting the property
132: */
133: public static void setProperty(String name, Object value, Object o,
134: Map context, boolean throwPropertyExceptions) {
135: Ognl.setTypeConverter(context, XWorkConverter.getInstance());
136:
137: Object oldRoot = Ognl.getRoot(context);
138: Ognl.setRoot(context, o);
139:
140: internalSetProperty(name, value, o, context,
141: throwPropertyExceptions);
142:
143: Ognl.setRoot(context, oldRoot);
144: }
145:
146: /**
147: * Looks for the real target with the specified property given a root Object which may be a
148: * CompoundRoot.
149: *
150: * @return the real target or null if no object can be found with the specified property
151: */
152: public static Object getRealTarget(String property, Map context,
153: Object root) throws OgnlException {
154: //special keyword, they must be cutting the stack
155: if ("top".equals(property)) {
156: return root;
157: }
158:
159: if (root instanceof CompoundRoot) {
160: // find real target
161: CompoundRoot cr = (CompoundRoot) root;
162:
163: try {
164: for (Iterator iterator = cr.iterator(); iterator
165: .hasNext();) {
166: Object target = iterator.next();
167:
168: if (OgnlRuntime.hasSetProperty(
169: (OgnlContext) context, target, property)
170: || OgnlRuntime.hasGetProperty(
171: (OgnlContext) context, target,
172: property)
173: || OgnlRuntime.getIndexedPropertyType(
174: (OgnlContext) context, target
175: .getClass(), property) != OgnlRuntime.INDEXED_PROPERTY_NONE) {
176: return target;
177: }
178: }
179: } catch (IntrospectionException ex) {
180: throw new OgnlException(
181: "Cannot figure out real target class", ex);
182: }
183:
184: return null;
185: }
186:
187: return root;
188: }
189:
190: /**
191: * Wrapper around Ognl.setValue() to handle type conversion for collection elements.
192: * Ideally, this should be handled by OGNL directly.
193: */
194: public static void setValue(String name, Map context, Object root,
195: Object value) throws OgnlException {
196: Ognl.setValue(compile(name), context, root, value);
197: }
198:
199: public static Object getValue(String name, Map context, Object root)
200: throws OgnlException {
201: return Ognl.getValue(compile(name), context, root);
202: }
203:
204: public static Object getValue(String name, Map context,
205: Object root, Class resultType) throws OgnlException {
206: return Ognl.getValue(compile(name), context, root, resultType);
207: }
208:
209: public static Object compile(String expression)
210: throws OgnlException {
211: synchronized (expressions) {
212: Object o = expressions.get(expression);
213:
214: if (o == null) {
215: o = Ognl.parseExpression(expression);
216: expressions.put(expression, o);
217: }
218:
219: return o;
220: }
221: }
222:
223: /**
224: * Copies the properties in the object "from" and sets them in the object "to"
225: * using specified type converter, or {@link com.opensymphony.xwork.util.XWorkConverter} if none
226: * is specified.
227: *
228: * @param from the source object
229: * @param to the target object
230: * @param context the action context we're running under
231: * @param exclusions collection of method names to excluded from copying ( can be null)
232: * @param inclusions collection of method names to included copying (can be null)
233: * note if exclusions AND inclusions are supplied and not null nothing will get copied.
234: */
235: public static void copy(Object from, Object to, Map context,
236: Collection exclusions, Collection inclusions) {
237: if (from == null || to == null) {
238: log
239: .warn("Attempting to copy from or to a null source. This is illegal and is bein skipped. This may be due to an error in an OGNL expression, action chaining, or some other event.");
240:
241: return;
242: }
243:
244: Map contextFrom = Ognl.createDefaultContext(from);
245: Ognl
246: .setTypeConverter(contextFrom, XWorkConverter
247: .getInstance());
248: Map contextTo = Ognl.createDefaultContext(to);
249: Ognl.setTypeConverter(contextTo, XWorkConverter.getInstance());
250:
251: PropertyDescriptor[] fromPds;
252: PropertyDescriptor[] toPds;
253:
254: try {
255: fromPds = getPropertyDescriptors(from);
256: toPds = getPropertyDescriptors(to);
257: } catch (IntrospectionException e) {
258: log.error("An error occured", e);
259:
260: return;
261: }
262:
263: Map toPdHash = new HashMap();
264:
265: for (int i = 0; i < toPds.length; i++) {
266: PropertyDescriptor toPd = toPds[i];
267: toPdHash.put(toPd.getName(), toPd);
268: }
269:
270: for (int i = 0; i < fromPds.length; i++) {
271: PropertyDescriptor fromPd = fromPds[i];
272: if (fromPd.getReadMethod() != null) {
273: boolean copy = true;
274: if (exclusions != null
275: && exclusions.contains(fromPd.getName())) {
276: copy = false;
277: } else if (inclusions != null
278: && !inclusions.contains(fromPd.getName())) {
279: copy = false;
280: }
281:
282: if (copy == true) {
283: PropertyDescriptor toPd = (PropertyDescriptor) toPdHash
284: .get(fromPd.getName());
285: if ((toPd != null)
286: && (toPd.getWriteMethod() != null)) {
287: try {
288: Object expr = OgnlUtil.compile(fromPd
289: .getName());
290: Object value = Ognl.getValue(expr,
291: contextFrom, from);
292: Ognl.setValue(expr, contextTo, to, value);
293: } catch (OgnlException e) {
294: // ignore, this is OK
295: }
296: }
297:
298: }
299:
300: }
301:
302: }
303: }
304:
305: /**
306: * Copies the properties in the object "from" and sets them in the object "to"
307: * using specified type converter, or {@link com.opensymphony.xwork.util.XWorkConverter} if none
308: * is specified.
309: *
310: * @param from the source object
311: * @param to the target object
312: * @param context the action context we're running under
313: */
314: public static void copy(Object from, Object to, Map context) {
315: OgnlUtil.copy(from, to, context, null, null);
316: }
317:
318: /**
319: * Get's the java beans property descriptors for the given source.
320: *
321: * @param source the source object.
322: * @return property descriptors.
323: * @throws IntrospectionException is thrown if an exception occurs during introspection.
324: */
325: public static PropertyDescriptor[] getPropertyDescriptors(
326: Object source) throws IntrospectionException {
327: BeanInfo beanInfo = getBeanInfo(source);
328: return beanInfo.getPropertyDescriptors();
329: }
330:
331: /**
332: * Creates a Map with read properties for the given source object.
333: * <p/>
334: * If the source object does not have a read property (i.e. write-only) then
335: * the property is added to the map with the value <code>here is no read method for property-name</code>.
336: *
337: * @param source the source object.
338: * @return a Map with (key = read property name, value = value of read property).
339: * @throws IntrospectionException is thrown if an exception occurs during introspection.
340: * @throws OgnlException is thrown by OGNL if the property value could not be retrieved
341: */
342: public static Map getBeanMap(Object source)
343: throws IntrospectionException, OgnlException {
344: Map beanMap = new HashMap();
345: Map sourceMap = Ognl.createDefaultContext(source);
346: PropertyDescriptor[] propertyDescriptors = getPropertyDescriptors(source);
347: for (int i = 0; i < propertyDescriptors.length; i++) {
348: PropertyDescriptor propertyDescriptor = propertyDescriptors[i];
349: String propertyName = propertyDescriptor.getDisplayName();
350: Method readMethod = propertyDescriptor.getReadMethod();
351: if (readMethod != null) {
352: Object expr = OgnlUtil.compile(propertyName);
353: Object value = Ognl.getValue(expr, sourceMap, source);
354: beanMap.put(propertyName, value);
355: } else {
356: beanMap.put(propertyName,
357: "There is no read method for " + propertyName);
358: }
359: }
360: return beanMap;
361: }
362:
363: /**
364: * Get's the java bean info for the given source.
365: *
366: * @param from the source object.
367: * @return java bean info.
368: * @throws IntrospectionException is thrown if an exception occurs during introspection.
369: */
370: public static BeanInfo getBeanInfo(Object from)
371: throws IntrospectionException {
372: synchronized (beanInfoCache) {
373: BeanInfo beanInfo;
374: beanInfo = (BeanInfo) beanInfoCache.get(from.getClass());
375: if (beanInfo == null) {
376: beanInfo = Introspector.getBeanInfo(from.getClass(),
377: Object.class);
378: beanInfoCache.put(from.getClass(), beanInfo);
379: }
380: return beanInfo;
381: }
382: }
383:
384: static void internalSetProperty(String name, Object value,
385: Object o, Map context, boolean throwPropertyExceptions) {
386: try {
387: setValue(name, context, o, value);
388: } catch (OgnlException e) {
389: Throwable reason = e.getReason();
390: String msg = "Caught OgnlException while setting property '"
391: + name
392: + "' on type '"
393: + o.getClass().getName()
394: + "'.";
395: Throwable exception = (reason == null) ? e : reason;
396:
397: if (throwPropertyExceptions) {
398: log.error(msg, exception);
399: throw new XworkException(msg, exception);
400: } else {
401: log.warn(msg, exception);
402: }
403: }
404: }
405: }
|