001: /*
002: * Copyright (c) 2002-2003 by OpenSymphony
003: * All rights reserved.
004: */
005: package com.opensymphony.webwork.views.xslt;
006:
007: import org.apache.commons.logging.Log;
008: import org.apache.commons.logging.LogFactory;
009: import org.w3c.dom.Node;
010: import org.w3c.dom.NodeList;
011:
012: import java.beans.IntrospectionException;
013: import java.beans.Introspector;
014: import java.beans.PropertyDescriptor;
015: import java.lang.reflect.Method;
016: import java.lang.reflect.InvocationTargetException;
017: import java.util.ArrayList;
018: import java.util.HashMap;
019: import java.util.List;
020: import java.util.Map;
021:
022: import com.opensymphony.webwork.WebWorkException;
023:
024: /**
025: * This class is the most general type of adapter, utilizing reflective introspection to present a DOM view of all of
026: * the public properties of its value. For example, a property returning a JavaBean such as:
027: *
028: * <pre>
029: * public Person getMyPerson() { ... }
030: * ...
031: * class Person {
032: * public String getFirstName();
033: * public String getLastName();
034: * }
035: * </pre>
036: *
037: * would be rendered as: <myPerson> <firstName>...</firstName> <lastName>...</lastName> </myPerson>
038: *
039: * @author <a href="mailto:meier@meisterbohne.de">Philipp Meier</a>
040: * @author Pat Niemeyer (pat@pat.net)
041: */
042: public class BeanAdapter extends AbstractAdapterElement {
043: //~ Static fields/initializers /////////////////////////////////////////////
044:
045: private static final Object[] NULLPARAMS = new Object[0];
046:
047: /**
048: * Cache can savely be static because the cached information is the same for all instances of this class.
049: */
050: private static Map propertyDescriptorCache;
051:
052: //~ Instance fields ////////////////////////////////////////////////////////
053:
054: private Log log = LogFactory.getLog(this .getClass());
055:
056: //~ Constructors ///////////////////////////////////////////////////////////
057:
058: public BeanAdapter() {
059: }
060:
061: public BeanAdapter(AdapterFactory adapterFactory,
062: AdapterNode parent, String propertyName, Object value) {
063: setContext(adapterFactory, parent, propertyName, value);
064: }
065:
066: //~ Methods ////////////////////////////////////////////////////////////////
067:
068: public String getTagName() {
069: return getPropertyName();
070: }
071:
072: public NodeList getChildNodes() {
073: NodeList nl = super .getChildNodes();
074: // Log child nodes for debug:
075: if (log.isDebugEnabled() && nl != null) {
076: log.debug("BeanAdapter getChildNodes for: " + getTagName());
077: log.debug(nl.toString());
078: }
079: return nl;
080: }
081:
082: protected List buildChildAdapters() {
083: log.debug("BeanAdapter building children. PropName = "
084: + getPropertyName());
085: List newAdapters = new ArrayList();
086: Class type = getPropertyValue().getClass();
087: PropertyDescriptor[] props = getPropertyDescriptors(getPropertyValue());
088:
089: if (props.length > 0) {
090: for (int i = 0; i < props.length; i++) {
091: Method m = props[i].getReadMethod();
092:
093: if (m == null) {
094: //FIXME: write only property or indexed access
095: continue;
096: }
097: if (log.isDebugEnabled())
098: log.debug("Bean reading property method: "
099: + m.getName());
100:
101: String propertyName = props[i].getName();
102: Object propertyValue;
103:
104: /*
105: Unwrap any invocation target exceptions and log them.
106: We really need a way to control which properties are accessed.
107: Perhaps with annotations in Java5?
108: */
109: try {
110: propertyValue = m.invoke(getPropertyValue(),
111: NULLPARAMS);
112: } catch (Exception e) {
113: if (e instanceof InvocationTargetException)
114: e = (Exception) ((InvocationTargetException) e)
115: .getTargetException();
116: log.error(e);
117: continue;
118: }
119:
120: Node childAdapter;
121:
122: if (propertyValue == null) {
123: childAdapter = getAdapterFactory().adaptNullValue(
124: this , propertyName);
125: } else {
126: childAdapter = getAdapterFactory().adaptNode(this ,
127: propertyName, propertyValue);
128: }
129:
130: if (childAdapter != null)
131: newAdapters.add(childAdapter);
132:
133: if (log.isDebugEnabled()) {
134: log
135: .debug(this + " adding adapter: "
136: + childAdapter);
137: }
138: }
139: } else {
140: // No properties found
141: log.info("Class " + type.getName()
142: + " has no readable properties, "
143: + " trying to adapt " + getPropertyName()
144: + " with StringAdapter...");
145:
146: //newAdapters.add(new StringAdapter(getRootAdapter(), this, getPropertyName(), getValue()));
147: }
148:
149: return newAdapters;
150: }
151:
152: /**
153: * Caching facade method to Introspector.getBeanInfo(Class, Class).getPropertyDescriptors();
154: */
155: private synchronized PropertyDescriptor[] getPropertyDescriptors(
156: Object bean) {
157: try {
158: if (propertyDescriptorCache == null) {
159: propertyDescriptorCache = new HashMap();
160: }
161:
162: PropertyDescriptor[] props = (PropertyDescriptor[]) propertyDescriptorCache
163: .get(bean.getClass());
164:
165: if (props == null) {
166: log.debug("Caching property descriptor for "
167: + bean.getClass().getName());
168: props = Introspector.getBeanInfo(bean.getClass(),
169: Object.class).getPropertyDescriptors();
170: propertyDescriptorCache.put(bean.getClass(), props);
171: }
172:
173: return props;
174: } catch (IntrospectionException e) {
175: e.printStackTrace();
176: throw new WebWorkException(
177: "Error getting property descriptors for " + bean
178: + " : " + e.getMessage());
179: }
180: }
181: }
|