View Javadoc

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