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 }