View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.beanutils;
19  
20  import java.io.Serializable;
21  import java.sql.Date;
22  import java.sql.ResultSet;
23  import java.sql.ResultSetMetaData;
24  import java.sql.SQLException;
25  import java.sql.Time;
26  import java.sql.Timestamp;
27  import java.util.ArrayList;
28  import java.util.HashMap;
29  import java.util.Map;
30  
31  /**
32   * <p>Provides common logic for JDBC implementations of {@link DynaClass}.</p>
33   *
34   * @author   Craig R. McClanahan
35   * @author   George Franciscus
36   * @version $Revision: 926529 $ $Date: 2010-03-23 07:44:24 -0400 (Tue, 23 Mar 2010) $
37   */
38  
39  abstract class JDBCDynaClass implements DynaClass, Serializable {
40  
41      // ----------------------------------------------------- Instance Variables
42  
43      /**
44       * <p>Flag defining whether column names should be lower cased when
45       * converted to property names.</p>
46       */
47      protected boolean lowerCase = true;
48  
49      /**
50       * <p>Flag defining whether column names or labels should be used.
51       */
52      private boolean useColumnLabel;
53  
54      /**
55       * <p>The set of dynamic properties that are part of this
56       * {@link DynaClass}.</p>
57       */
58      protected DynaProperty[] properties = null;
59  
60      /**
61       * <p>The set of dynamic properties that are part of this
62       * {@link DynaClass}, keyed by the property name.  Individual descriptor
63       * instances will be the same instances as those in the
64       * <code>properties</code> list.</p>
65       */
66      protected Map propertiesMap = new HashMap();
67  
68      /**
69       * Cross Reference for column name --> dyna property name
70       * (needed when lowerCase option is true)
71       */
72      private Map columnNameXref;
73  
74      // ------------------------------------------------------ DynaClass Methods
75  
76      /**
77       * <p>Return the name of this DynaClass (analogous to the
78       * <code>getName()</code> method of <code>java.lang.Class</code), which
79       * allows the same <code>DynaClass</code> implementation class to support
80       * different dynamic classes, with different sets of properties.</p>
81       */
82      public String getName() {
83  
84          return (this.getClass().getName());
85  
86      }
87  
88      /**
89       * <p>Return a property descriptor for the specified property, if it
90       * exists; otherwise, return <code>null</code>.</p>
91       *
92       * @param name Name of the dynamic property for which a descriptor
93       *  is requested
94       *
95       * @exception IllegalArgumentException if no property name is specified
96       */
97      public DynaProperty getDynaProperty(String name) {
98  
99          if (name == null) {
100             throw new IllegalArgumentException("No property name specified");
101         }
102         return ((DynaProperty) propertiesMap.get(name));
103 
104     }
105 
106     /**
107      * <p>Return an array of <code>ProperyDescriptors</code> for the properties
108      * currently defined in this DynaClass.  If no properties are defined, a
109      * zero-length array will be returned.</p>
110      */
111     public DynaProperty[] getDynaProperties() {
112 
113         return (properties);
114 
115     }
116 
117     /**
118      * <p>Instantiate and return a new DynaBean instance, associated
119      * with this DynaClass.  <strong>NOTE</strong> - This operation is not
120      * supported, and throws an exception.</p>
121      *
122      * @exception IllegalAccessException if the Class or the appropriate
123      *  constructor is not accessible
124      * @exception InstantiationException if this Class represents an abstract
125      *  class, an array class, a primitive type, or void; or if instantiation
126      *  fails for some other reason
127      */
128     public DynaBean newInstance()
129             throws IllegalAccessException, InstantiationException {
130 
131         throw new UnsupportedOperationException("newInstance() not supported");
132 
133     }
134 
135     /**
136      * Set whether the column label or name should be used for the property name.
137      *
138      * @param useColumnLabel true if the column label should be used, otherwise false
139      */
140     public void setUseColumnLabel(boolean useColumnLabel) {
141         this.useColumnLabel = useColumnLabel;
142     }
143 
144     /**
145      * <p>Loads and returns the <code>Class</code> of the given name.
146      * By default, a load from the thread context class loader is attempted.
147      * If there is no such class loader, the class loader used to load this
148      * class will be utilized.</p>
149      *
150      * @param className The name of the class to load
151      * @return The loaded class
152      * @exception SQLException if an exception was thrown trying to load
153      *  the specified class
154      */
155     protected Class loadClass(String className) throws SQLException {
156 
157         try {
158             ClassLoader cl = Thread.currentThread().getContextClassLoader();
159             if (cl == null) {
160                     cl = this.getClass().getClassLoader();
161             }
162             // use Class.forName() - see BEANUTILS-327
163             return Class.forName(className, false, cl);
164         } catch (Exception e) {
165             throw new SQLException(
166                     "Cannot load column class '" + className + "': " + e);
167         }
168 
169     }
170 
171     /**
172      * <p>Factory method to create a new DynaProperty for the given index
173      * into the result set metadata.</p>
174      * 
175      * @param metadata is the result set metadata
176      * @param i is the column index in the metadata
177      * @return the newly created DynaProperty instance
178      * @throws SQLException If an error occurs accessing the SQL metadata
179      */
180     protected DynaProperty createDynaProperty(
181                                     ResultSetMetaData metadata,
182                                     int i)
183                                     throws SQLException {
184 
185         String columnName = null;
186         if (useColumnLabel) {
187             columnName = metadata.getColumnLabel(i);
188         }
189         if (columnName == null || columnName.trim().length() == 0) {
190             columnName = metadata.getColumnName(i);
191         }
192         String name = lowerCase ? columnName.toLowerCase() : columnName;
193         if (!name.equals(columnName)) {
194             if (columnNameXref == null) {
195                 columnNameXref = new HashMap();
196             }
197             columnNameXref.put(name, columnName);
198         }
199         String className = null;
200         try {
201             int sqlType = metadata.getColumnType(i);
202             switch (sqlType) {
203                 case java.sql.Types.DATE:
204                     return new DynaProperty(name, java.sql.Date.class);
205                 case java.sql.Types.TIMESTAMP:
206                     return new DynaProperty(name, java.sql.Timestamp.class);
207                 case java.sql.Types.TIME:
208                     return new DynaProperty(name, java.sql.Time.class);
209                 default:
210                     className = metadata.getColumnClassName(i);
211             }
212         } catch (SQLException e) {
213             // this is a patch for HsqlDb to ignore exceptions
214             // thrown by its metadata implementation
215         }
216 
217         // Default to Object type if no class name could be retrieved
218         // from the metadata
219         Class clazz = Object.class;
220         if (className != null) {
221             clazz = loadClass(className);
222         }
223         return new DynaProperty(name, clazz);
224 
225     }
226 
227     /**
228      * <p>Introspect the metadata associated with our result set, and populate
229      * the <code>properties</code> and <code>propertiesMap</code> instance
230      * variables.</p>
231      *
232      * @param resultSet The <code>resultSet</code> whose metadata is to
233      *  be introspected
234      *
235      * @exception SQLException if an error is encountered processing the
236      *  result set metadata
237      */
238     protected void introspect(ResultSet resultSet) throws SQLException {
239 
240         // Accumulate an ordered list of DynaProperties
241         ArrayList list = new ArrayList();
242         ResultSetMetaData metadata = resultSet.getMetaData();
243         int n = metadata.getColumnCount();
244         for (int i = 1; i <= n; i++) { // JDBC is one-relative!
245             DynaProperty dynaProperty = createDynaProperty(metadata, i);
246             if (dynaProperty != null) {
247                     list.add(dynaProperty);
248             }
249         }
250 
251         // Convert this list into the internal data structures we need
252         properties =
253             (DynaProperty[]) list.toArray(new DynaProperty[list.size()]);
254         for (int i = 0; i < properties.length; i++) {
255             propertiesMap.put(properties[i].getName(), properties[i]);
256         }
257 
258     }
259 
260     /**
261      * Get a column value from a {@link ResultSet} for the specified name.
262      *
263      * @param resultSet The result set
264      * @param name The property name
265      * @return The value
266      * @throws SQLException if an error occurs
267      */
268     protected Object getObject(ResultSet resultSet, String name) throws SQLException {
269 
270         DynaProperty property = getDynaProperty(name);
271         if (property == null) {
272             throw new IllegalArgumentException("Invalid name '" + name + "'");
273         }
274         String columnName = getColumnName(name);
275         Class type = property.getType();
276 
277         // java.sql.Date
278         if (type.equals(Date.class)) {
279             return resultSet.getDate(columnName);
280         }
281 
282         // java.sql.Timestamp
283         if (type.equals(Timestamp.class)) {
284             return resultSet.getTimestamp(columnName);
285         }
286 
287         // java.sql.Time
288         if (type.equals(Time.class)) {
289             return resultSet.getTime(columnName);
290         }
291 
292         return resultSet.getObject(columnName);
293     }
294 
295     /**
296      * Get the table column name for the specified property name.
297      * 
298      * @param name The property name
299      * @return The column name (which can be different if the <i>lowerCase</i>
300      * option is used).
301      */
302     protected String getColumnName(String name) {
303         if (columnNameXref != null && columnNameXref.containsKey(name)) {
304             return (String)columnNameXref.get(name);
305         } else {
306             return name;
307         }
308     }
309 
310 }