View Javadoc

1   /*
2    * $Id: BeanAdapter.java 439747 2006-09-03 09:22:46Z mrdon $
3    *
4    * Copyright 2006 The Apache Software Foundation.
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    *
10   *      http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.struts2.views.xslt;
19  
20  import java.beans.IntrospectionException;
21  import java.beans.Introspector;
22  import java.beans.PropertyDescriptor;
23  import java.lang.reflect.InvocationTargetException;
24  import java.lang.reflect.Method;
25  import java.util.ArrayList;
26  import java.util.HashMap;
27  import java.util.List;
28  import java.util.Map;
29  
30  import org.apache.commons.logging.Log;
31  import org.apache.commons.logging.LogFactory;
32  import org.apache.struts2.StrutsException;
33  import org.w3c.dom.Node;
34  import org.w3c.dom.NodeList;
35  
36  
37  /***
38   * This class is the most general type of adapter, utilizing reflective introspection to present a DOM view of all of
39   * the public properties of its value.  For example, a property returning a JavaBean such as:
40   *
41   * <pre>
42   * public Person getMyPerson() { ... }
43   * ...
44   * class Person {
45   * 		public String getFirstName();
46   * 		public String getLastName();
47   * }
48   * </pre>
49   *
50   * would be rendered as: <myPerson> <firstName>...</firstName> <lastName>...</lastName> </myPerson>
51   */
52  public class BeanAdapter extends AbstractAdapterElement {
53      //~ Static fields/initializers /////////////////////////////////////////////
54  
55      private static final Object[] NULLPARAMS = new Object[0];
56  
57      /***
58       * Cache can savely be static because the cached information is the same for all instances of this class.
59       */
60      private static Map<Class, PropertyDescriptor[]> propertyDescriptorCache;
61  
62      //~ Instance fields ////////////////////////////////////////////////////////
63  
64      private Log log = LogFactory.getLog(this.getClass());
65  
66      //~ Constructors ///////////////////////////////////////////////////////////
67  
68      public BeanAdapter() {
69      }
70  
71      public BeanAdapter(
72              AdapterFactory adapterFactory, AdapterNode parent, String propertyName, Object value) {
73          setContext(adapterFactory, parent, propertyName, value);
74      }
75  
76      //~ Methods ////////////////////////////////////////////////////////////////
77  
78      public String getTagName() {
79          return getPropertyName();
80      }
81  
82      public NodeList getChildNodes() {
83          NodeList nl = super.getChildNodes();
84          // Log child nodes for debug:
85          if (log.isDebugEnabled() && nl != null) {
86              log.debug("BeanAdapter getChildNodes for: " + getTagName());
87              log.debug(nl.toString());
88          }
89          return nl;
90      }
91  
92      protected List<Node> buildChildAdapters() {
93          log.debug("BeanAdapter building children.  PropName = " + getPropertyName());
94          List<Node> newAdapters = new ArrayList<Node>();
95          Class type = getPropertyValue().getClass();
96          PropertyDescriptor[] props = getPropertyDescriptors(getPropertyValue());
97  
98          if (props.length > 0) {
99              for (PropertyDescriptor prop : props) {
100                 Method m = prop.getReadMethod();
101                 log.debug("Bean reading property method: " + m.getName());
102 
103                 if (m == null) {
104                     //FIXME: write only property or indexed access
105                     continue;
106                 }
107 
108                 String propertyName = prop.getName();
109                 Object propertyValue;
110 
111                 /*
112                     Unwrap any invocation target exceptions and log them.
113                     We really need a way to control which properties are accessed.
114                     Perhaps with annotations in Java5?
115                 */
116                 try {
117                     propertyValue = m.invoke(getPropertyValue(), NULLPARAMS);
118                 } catch (Exception e) {
119                     if (e instanceof InvocationTargetException)
120                         e = (Exception) ((InvocationTargetException) e).getTargetException();
121                     log.error(e);
122                     continue;
123                 }
124 
125                 Node childAdapter;
126 
127                 if (propertyValue == null) {
128                     childAdapter = getAdapterFactory().adaptNullValue(this, propertyName);
129                 } else {
130                     childAdapter = getAdapterFactory().adaptNode(this, propertyName, propertyValue);
131                 }
132 
133                 if (childAdapter != null)
134                     newAdapters.add(childAdapter);
135 
136                 if (log.isDebugEnabled()) {
137                     log.debug(this + " adding adapter: " + childAdapter);
138                 }
139             }
140         } else {
141             // No properties found
142             log.info(
143                     "Class " + type.getName() + " has no readable properties, " + " trying to adapt " + getPropertyName() + " with StringAdapter...");
144         }
145 
146         return newAdapters;
147     }
148 
149     /***
150      * Caching facade method to Introspector.getBeanInfo(Class, Class).getPropertyDescriptors();
151      */
152     private synchronized PropertyDescriptor[] getPropertyDescriptors(Object bean) {
153         try {
154             if (propertyDescriptorCache == null) {
155                 propertyDescriptorCache = new HashMap<Class, PropertyDescriptor[]>();
156             }
157 
158             PropertyDescriptor[] props = propertyDescriptorCache.get(bean.getClass());
159 
160             if (props == null) {
161                 log.debug("Caching property descriptor for " + bean.getClass().getName());
162                 props = Introspector.getBeanInfo(bean.getClass(), Object.class).getPropertyDescriptors();
163                 propertyDescriptorCache.put(bean.getClass(), props);
164             }
165 
166             return props;
167         } catch (IntrospectionException e) {
168             e.printStackTrace();
169             throw new StrutsException("Error getting property descriptors for " + bean + " : " + e.getMessage());
170         }
171     }
172 }