001     /*
002     * Copyright 2001,2004 The Apache Software Foundation.
003     * 
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     * 
008     *      http://www.apache.org/licenses/LICENSE-2.0
009     * 
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    
017    package org.apache.commons.scaffold.sql;
018    
019    
020    import java.beans.PropertyDescriptor;
021    import java.lang.reflect.InvocationTargetException;
022    import java.lang.reflect.Method;
023    import java.sql.ResultSet;
024    import java.sql.ResultSetMetaData;
025    import java.sql.SQLException;
026    import java.sql.Types;
027    import java.util.ArrayList;
028    import java.util.Collection;
029    import java.util.HashMap;
030    import java.util.Iterator;
031    import java.util.Map;
032    
033    import org.apache.commons.beanutils.BeanUtils;
034    import org.apache.commons.beanutils.PropertyUtils;
035    
036    
037     /**
038      * General purpose utility methods related to ResultSets
039      *
040      * @author Ted Husted
041      * @version $Revision: 561366 $ $Date: 2007-07-31 16:58:29 +0100 (Tue, 31 Jul 2007) $
042      */
043     public class ResultSetUtils {
044    
045    
046         /**
047          * Returns next record of result set as a Map.
048          * The keys of the map are the column names,
049          * as returned by the metadata.
050          * The values are the columns as Objects.
051          *
052          * @param resultSet The ResultSet to process.
053          * @exception SQLException if an error occurs.
054          */
055         public static Map getMap(ResultSet resultSet)
056            throws SQLException {
057    
058                // Acquire resultSet MetaData
059            ResultSetMetaData metaData = resultSet.getMetaData();
060            int cols = metaData.getColumnCount();
061    
062                // Create hashmap, sized to number of columns
063            HashMap row = new HashMap(cols,1);
064    
065                // Transfer record into hashmap
066            if (resultSet.next()) {
067                for (int i=1; i<=cols ; i++) {
068                    row.put(metaData.getColumnName(i),
069                        resultSet.getObject(i));
070                }
071            } // end while
072    
073            return ((Map) row);
074    
075         } // end getMap
076    
077    
078         /**
079          * Return a Collection of Maps, each representing
080          * a row from the ResultSet.
081          * The keys of the map are the column names,
082          * as returned by the metadata.
083          * The values are the columns as Objects.
084          *
085          * @param resultSet The ResultSet to process.
086          * @exception SQLException if an error occurs.
087          */
088         public static Collection getMaps(ResultSet resultSet)
089            throws SQLException {
090    
091                // Acquire resultSet MetaData
092            ResultSetMetaData metaData = resultSet.getMetaData();
093            int cols = metaData.getColumnCount();
094    
095                // Use ArrayList to maintain ResultSet sequence
096            ArrayList list = new ArrayList();
097    
098                // Scroll to each record, make map of row, add to list
099            while (resultSet.next()) {
100                HashMap row = new HashMap(cols,1);
101                for (int i=1; i<=cols ; i++) {
102                    row.put(metaData.getColumnName(i),
103                        resultSet.getString(i));
104                }
105                list.add(row);
106            } // end while
107    
108            return ((Collection) list);
109    
110         } // end getMaps
111    
112    
113        /**
114         * Populate the JavaBean properties of the specified bean, based on
115         * the specified name/value pairs.  This method uses Java reflection APIs
116         * to identify corresponding "property setter" method names. The type of
117         * the value in the Map must match the setter type. The setter must
118         * expect a single arguement (the one on the Map).
119         * <p>
120         * The particular setter method to be called for each property is
121         * determined using the usual JavaBeans introspection mechanisms. Thus,
122         * you may identify custom setter methods using a BeanInfo class that is
123         * associated with the class of the bean itself. If no such BeanInfo
124         * class is available, the standard method name conversion ("set" plus
125         * the capitalized name of the property in question) is used.
126         * <p>
127         * <strong>NOTE</strong>:  It is contrary to the JavaBeans Specification
128         * to have more than one setter method (with different argument
129         * signatures) for the same property.
130         * <p>
131         * This method adopted from the Apache Commons BeanUtils.populate.
132         *
133         * @author Craig R. McClanahan
134         * @author Ralph Schaer
135         * @author Chris Audley
136         * @author Rey Fran�ois
137         * @author Gregor Ra�man
138         * @author Ted Husted
139         *
140         * @param bean JavaBean whose properties are being populated
141         * @param properties Map keyed by property name, with the
142         *  corresponding value to be set
143         *
144         * @exception IllegalAccessException if the caller does not have
145         *  access to the property accessor method
146         * @exception InvocationTargetException if the property accessor method
147         *  throws an exception
148         * @deprecated Use BeanUtils.CopyProperties instead.
149         */
150        public static void setProperties(Object bean, Map properties)
151            throws IllegalAccessException, InvocationTargetException {
152    
153            if ((bean == null) || (properties == null))
154                return;
155    
156            /*
157            if (debug >= 1)
158                System.out.println("BeanUtils.populate(" + bean + ", " +
159                                   properties + ")");
160            */
161    
162            // Loop through the property name/value pairs to be set
163            Iterator names = properties.keySet().iterator();
164            while (names.hasNext()) {
165    
166                // Identify the property name and value(s) to be assigned
167                String name = (String) names.next();
168                if (name == null)
169                    continue;
170    
171                // Get the property descriptor of the requested property (if any)
172                PropertyDescriptor descriptor = null;
173                try {
174                    descriptor = PropertyUtils.getPropertyDescriptor(bean, name);
175                } catch (Throwable t) {
176                    /*
177                    if (debug >= 1)
178                        System.out.println("    getPropertyDescriptor: " + t);
179                    */
180                    descriptor = null;
181                }
182                if (descriptor == null) {
183                    /*
184                    if (debug >= 1)
185                        System.out.println("    No such property, skipping");
186                    */
187                    continue;
188                }
189    
190                /*
191                if (debug >= 1)
192                    System.out.println("    Property descriptor is '" +
193                                       descriptor + "'");
194                */
195    
196                // Identify the relevant setter method (if there is one)
197                Method setter = descriptor.getWriteMethod();
198                if (setter == null) {
199                    /*
200                    if (debug >= 1)
201                        System.out.println("    No setter method, skipping");
202                    */
203                    continue;
204                }
205    
206                // Obtain value to be set
207                Object[] args = new Object[1];
208                args[0] = properties.get(name); // This MUST match setter type
209    
210                /*
211                if (debug >= 1)
212                    System.out.println("  name='" + name + "', value.class='" +
213                                       (value == null ? "NONE" :
214                                       value.getClass().getName()) + "'");
215                */
216                /*
217                if (debug >= 1)
218                    System.out.println("    Setting to " +
219                                       (parameters[0] == null ? "NULL" :
220                                        "'" + parameters[0] + "'"));
221                */
222    
223                // Invoke the setter method
224                setter.invoke(bean,args);
225            }
226    
227            /*
228            if (debug >= 1)
229                System.out.println("============================================");
230            */
231    
232        } // end setProperties
233    
234    
235        /**
236         * Map JDBC objects to Java equivalents.
237         * Used by getBean() and getBeans().
238         * <p>
239         * Some types not supported.
240         * Many not work with all drivers.
241         * <p>
242         * Makes binary conversions of BIGINT, DATE, DECIMAL, DOUBLE, FLOAT, INTEGER,
243         * REAL, SMALLINT, TIME, TIMESTAMP, TINYINT.
244         * Makes Sting conversions of CHAR, CLOB, VARCHAR, LONGVARCHAR, BLOB, LONGVARBINARY,
245         * VARBINARY.
246         * <p>
247         * DECIMAL, INTEGER, SMALLINT, TIMESTAMP, CHAR, VARCHAR tested with MySQL and Poolman.
248         * Others not guaranteed.
249         */
250        public static void putEntry(
251                Map properties,
252                ResultSetMetaData metaData,
253                ResultSet resultSet,
254                int i)
255            throws SQLException {
256    
257            /*
258            In a perfect universe, this would be enough
259                properties.put(
260                    metaData.getColumnName(i),
261                    resultSet.getObject(i));
262            But only String, Timestamp, and Integer seem to get through that way.
263            */
264    
265            String columnName = metaData.getColumnName(i);
266    
267            switch (metaData.getColumnType(i)) {
268    
269                // http://java.sun.com/j2se/1.3.0/docs/api/java/sql/Types.html
270    
271                case Types.BIGINT:
272                    properties.put(columnName,
273                        new Long(resultSet.getLong(i)));
274                    break;
275    
276                case Types.DATE:
277                    properties.put(columnName,
278                        resultSet.getDate(i));
279                    break;
280    
281                case Types.DECIMAL:
282                case Types.DOUBLE:
283                    properties.put(columnName,
284                        new Double(resultSet.getDouble(i)));
285                    break;
286    
287                case Types.FLOAT:
288                    properties.put(columnName,
289                        new Float(resultSet.getFloat(i)));
290                    break;
291    
292                case Types.INTEGER:
293                    properties.put(columnName,
294                        new Integer(resultSet.getInt(i)));
295                    break;
296    
297                case Types.REAL:
298                    properties.put(columnName,
299                        new Double(resultSet.getString(i)));
300                    break;
301    
302                case Types.SMALLINT:
303                    properties.put(columnName,
304                        new Short(resultSet.getShort(i)));
305                    break;
306    
307                case Types.TIME:
308                    properties.put(columnName,
309                        resultSet.getTime(i));
310                    break;
311    
312                case Types.TIMESTAMP:
313                    properties.put(columnName,
314                        resultSet.getTimestamp(i));
315                    break;
316    
317                // :FIXME: Throws java.lang.ClassCastException: java.lang.Integer
318                // :FIXME: with Poolman and MySQL unless use getString.
319                case Types.TINYINT:
320                    properties.put(columnName,
321                        new Byte(resultSet.getString(i)));
322                    break;
323    
324                case Types.CHAR:
325                case Types.CLOB:
326                case Types.VARCHAR:
327                case Types.LONGVARCHAR:
328                    // :FIXME: Handle binaries differently?
329                case Types.BLOB:
330                case Types.LONGVARBINARY:
331                case Types.VARBINARY:
332                    properties.put(columnName,
333                        resultSet.getString(i));
334                   break;
335    
336                /*
337                    :FIXME: Add handlers for
338                    ARRAY
339                    BINARY
340                    BIT
341                    DISTINCT
342                    JAVA_OBJECT
343                    NULL
344                    NUMERIC
345                    OTHER
346                    REF
347                    STRUCT
348                */
349    
350                 // Otherwise, pass as *String property to be converted
351                default:
352                    properties.put(columnName + "String",
353                        resultSet.getString(i));
354                    break;
355            } // end switch
356    
357        } // end putEntry
358    
359    
360        /**
361         * Populate target bean with the first record from a ResultSet.
362         *
363         * @param resultSet The ResultSet whose parameters are to be used
364         * to populate bean properties
365         * @param target An instance of the bean to populate
366         * @exception SQLException if an exception is thrown while setting
367         * property values, populating the bean, or accessing the ResultSet
368         * @return True if resultSet contained a next element
369         */
370        public static boolean getElement(Object target, ResultSet resultSet)
371            throws SQLException {
372    
373                // Check prerequisites
374            if ((target==null) || (resultSet==null))
375                throw new SQLException("getElement: Null parameter");
376    
377                // Acquire resultSet MetaData
378            ResultSetMetaData metaData = resultSet.getMetaData();
379            int cols = metaData.getColumnCount();
380    
381                // Create hashmap, sized to number of columns
382            HashMap properties = new HashMap(cols,1);
383    
384                // Scroll to next record and pump into hashmap
385            boolean found = false;
386            if (resultSet.next()) {
387                found = true;
388                for (int i=1; i<=cols ; i++) {
389                    putEntry(properties,metaData,resultSet,i);
390                }
391                try {
392                    BeanUtils.copyProperties(target,properties);
393                }
394                catch (Throwable t) {
395                    throw new SQLException("ResultSetUtils.getElement: " +
396                        t.getMessage() + properties.toString());
397                }
398    
399            } // end if
400    
401            return found;
402    
403         } // end getElement
404    
405    
406        /**
407         * Return a ArrayList of beans populated from a ResultSet.
408         *
409         * @param resultSet The ResultSet whose parameters are to be used
410         * to populate bean properties
411         * @param target An instance of the bean to populate
412         * @exception SQLException if an exception is thrown while setting
413         * property values, populating the bean, or accessing the ResultSet
414         */
415         public static Collection getCollection(Object target, ResultSet resultSet)
416            throws SQLException {
417    
418                // Check prerequisites
419            if ((target==null) || (resultSet==null))
420                throw new SQLException("getCollection: Null parameter");
421    
422                // Acquire resultSet MetaData
423            ResultSetMetaData metaData = resultSet.getMetaData();
424            int cols = metaData.getColumnCount();
425    
426                // Create hashmap, sized to number of columns
427            HashMap properties = new HashMap(cols,1);
428    
429                // Use ArrayList to maintain ResultSet sequence
430            ArrayList list = new ArrayList();
431    
432                // Acquire target class
433            Class factory = target.getClass();
434    
435                // Scroll to next record and pump into hashmap
436            while (resultSet.next()) {
437                for (int i=1; i<=cols ; i++) {
438                    putEntry(properties,metaData,resultSet,i);
439                }
440                try {
441                    Object bean = factory.newInstance();
442                    BeanUtils.copyProperties(bean,properties);
443                    list.add(bean);
444                }
445                catch (Throwable t) {
446                    throw new SQLException("RequestUtils.getCollection: " +
447                        t.getMessage());
448                }
449    
450                properties.clear();
451    
452            } // end while
453    
454            return ((Collection) list);
455    
456         } // end getCollection
457    
458    
459    } // end ResultSetUtils
460