001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.commons.dbutils.handlers;
018    
019    import java.sql.ResultSet;
020    import java.sql.SQLException;
021    import java.util.HashMap;
022    import java.util.Map;
023    
024    import org.apache.commons.dbutils.ResultSetHandler;
025    import org.apache.commons.dbutils.RowProcessor;
026    
027    /**
028     * <p>
029     * <code>ResultSetHandler</code> implementation that returns a Map of Maps.
030     * <code>ResultSet</code> rows are converted into Maps which are then stored
031     * in a Map under the given key.  Although this implementation uses Maps to 
032     * store row contents, subclasses are encouraged to override the 
033     * <code>createRow()</code> method to convert the rows into any kind of object.
034     * </p>
035     * <p>
036     * If you had a Person table with a primary key column called ID, you could 
037     * retrieve rows from the table like this:
038     * <pre>
039     * ResultSetHandler h = new KeyedHandler("id");
040     * Map found = (Map) queryRunner.query("select id, name, age from person", h);
041     * Map jane = (Map) found.get(new Long(1)); // jane's id is 1
042     * String janesName = (String) jane.get("name");
043     * Integer janesAge = (Integer) jane.get("age");
044     * </pre>
045     * Note that the "id" passed to KeyedHandler and "name" and "age" passed to the
046     * returned Map's get() method can be in any case.  The data types returned for
047     * name and age are dependent upon how your JDBC driver converts SQL column 
048     * types from the Person table into Java types.  
049     * </p>
050     * <p>
051     * To avoid these type issues you could subclass KeyedHandler and override 
052     * <code>createRow()</code> to store rows in Java bean instances (ie. a
053     * Person class).
054     * </p>
055     * <p>This class is thread safe.</p>
056     * 
057     * @see org.apache.commons.dbutils.ResultSetHandler
058     * @since DbUtils 1.1
059     */
060    public class KeyedHandler implements ResultSetHandler {
061    
062        /**
063         * The RowProcessor implementation to use when converting rows
064         * into Objects.
065         */
066        protected final RowProcessor convert;
067    
068        /**
069         * The column index to retrieve key values from.  Defaults to 1.
070         */
071        protected final int columnIndex;
072    
073        /**
074         * The column name to retrieve key values from.  Either columnName or 
075         * columnIndex will be used but never both.
076         */
077        protected final String columnName;
078    
079        /** 
080         * Creates a new instance of KeyedHandler.  The value of the first column 
081         * of each row will be a key in the Map.
082         */
083        public KeyedHandler() {
084            this(ArrayHandler.ROW_PROCESSOR, 1, null);
085        }
086    
087        /**
088         * Creates a new instance of KeyedHandler.  The value of the first column 
089         * of each row will be a key in the Map.
090         *
091         * @param convert The <code>RowProcessor</code> implementation
092         * to use when converting rows into Maps
093         */
094        public KeyedHandler(RowProcessor convert) {
095            this(convert, 1, null);
096        }
097    
098        /** 
099         * Creates a new instance of KeyedHandler.
100         * 
101         * @param columnIndex The values to use as keys in the Map are 
102         * retrieved from the column at this index.
103         */
104        public KeyedHandler(int columnIndex) {
105            this(ArrayHandler.ROW_PROCESSOR, columnIndex, null);
106        }
107    
108        /** 
109         * Creates a new instance of KeyedHandler.
110         * 
111         * @param columnName The values to use as keys in the Map are 
112         * retrieved from the column with this name.
113         */
114        public KeyedHandler(String columnName) {
115            this(ArrayHandler.ROW_PROCESSOR, 1, columnName);
116        }
117    
118        // Helper
119        private KeyedHandler(RowProcessor convert, int columnIndex,
120                String columnName) {
121            super();
122            this.convert = convert;
123            this.columnIndex = columnIndex;
124            this.columnName = columnName;
125        }
126    
127        /**
128         * Convert each row's columns into a Map and store then 
129         * in a <code>Map</code> under <code>ResultSet.getObject(key)</code> key.
130         * 
131         * @return A <code>Map</code> of Maps, never <code>null</code>. 
132         * @throws SQLException if a database access error occurs
133         * @see org.apache.commons.dbutils.ResultSetHandler#handle(java.sql.ResultSet)
134         */
135        public Object handle(ResultSet rs) throws SQLException {
136            Map result = createMap();
137            while (rs.next()) {
138                result.put(createKey(rs), createRow(rs));
139            }
140            return result;
141        }
142    
143        /**
144         * This factory method is called by <code>handle()</code> to create the Map
145         * to store records in.  This implementation returns a <code>HashMap</code>
146         * instance.
147         *
148         * @return Map to store records in
149         */
150        protected Map createMap() {
151            return new HashMap();
152        }
153    
154        /**
155         * This factory method is called by <code>handle()</code> to retrieve the
156         * key value from the current <code>ResultSet</code> row.  This 
157         * implementation returns <code>ResultSet.getObject()</code> for the 
158         * configured key column name or index. 
159         * @param rs ResultSet to create a key from
160         * @return Object from the configured key column name/index
161         * @throws SQLException if a database access error occurs
162         */
163        protected Object createKey(ResultSet rs) throws SQLException {
164            return (columnName == null) ? rs.getObject(columnIndex) : rs
165                    .getObject(columnName);
166        }
167    
168        /**
169         * This factory method is called by <code>handle()</code> to store the
170         * current <code>ResultSet</code> row in some object. This 
171         * implementation returns a <code>Map</code> with case insensitive column
172         * names as keys.  Calls to <code>map.get("COL")</code> and 
173         * <code>map.get("col")</code> return the same value.
174         * @param rs ResultSet to create a row from
175         * @return Object typed Map containing column names to values
176         * @throws SQLException if a database access error occurs
177         */
178        protected Object createRow(ResultSet rs) throws SQLException {
179            return this.convert.toMap(rs);
180        }
181    
182    }