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 */
017package org.apache.commons.dbutils.handlers;
018
019import java.sql.ResultSet;
020import java.sql.SQLException;
021import java.util.Map;
022
023import org.apache.commons.dbutils.RowProcessor;
024
025/**
026 * <p>
027 * {@code ResultSetHandler} implementation that returns a Map of Maps.
028 * {@code ResultSet} rows are converted into Maps which are then stored
029 * in a Map under the given key.
030 * </p>
031 * <p>
032 * If you had a Person table with a primary key column called ID, you could
033 * retrieve rows from the table like this:
034 * <pre>
035 * ResultSetHandler h = new KeyedHandler("id");
036 * Map found = (Map) queryRunner.query("select id, name, age from person", h);
037 * Map jane = (Map) found.get(new Long(1)); // jane's id is 1
038 * String janesName = (String) jane.get("name");
039 * Integer janesAge = (Integer) jane.get("age");
040 * </pre>
041 * Note that the "id" passed to KeyedHandler and "name" and "age" passed to the
042 * returned Map's get() method can be in any case.  The data types returned for
043 * name and age are dependent upon how your JDBC driver converts SQL column
044 * types from the Person table into Java types.
045 * &lt;/p&gt;
046 * <p>This class is thread safe.</p>
047 *
048 * @param <K> The type of the key
049 * @see org.apache.commons.dbutils.ResultSetHandler
050 * @since 1.1
051 */
052public class KeyedHandler<K> extends AbstractKeyedHandler<K, Map<String, Object>> {
053
054    /**
055     * The RowProcessor implementation to use when converting rows
056     * into Objects.
057     */
058    protected final RowProcessor convert;
059
060    /**
061     * The column index to retrieve key values from.  Defaults to 1.
062     */
063    protected final int columnIndex;
064
065    /**
066     * The column name to retrieve key values from.  Either columnName or
067     * columnIndex will be used but never both.
068     */
069    protected final String columnName;
070
071    /**
072     * Creates a new instance of KeyedHandler.  The value of the first column
073     * of each row will be a key in the Map.
074     */
075    public KeyedHandler() {
076        this(ArrayHandler.ROW_PROCESSOR, 1, null);
077    }
078
079    /**
080     * Creates a new instance of KeyedHandler.
081     *
082     * @param columnIndex The values to use as keys in the Map are
083     * retrieved from the column at this index.
084     */
085    public KeyedHandler(final int columnIndex) {
086        this(ArrayHandler.ROW_PROCESSOR, columnIndex, null);
087    }
088
089    /**
090     * Creates a new instance of KeyedHandler.  The value of the first column
091     * of each row will be a key in the Map.
092     *
093     * @param convert The {@code RowProcessor} implementation
094     * to use when converting rows into Maps
095     */
096    public KeyedHandler(final RowProcessor convert) {
097        this(convert, 1, null);
098    }
099
100    /** Private Helper
101     * @param convert The {@code RowProcessor} implementation
102     * to use when converting rows into Maps
103     * @param columnIndex The values to use as keys in the Map are
104     * retrieved from the column at this index.
105     * @param columnName The values to use as keys in the Map are
106     * retrieved from the column with this name.
107     */
108    private KeyedHandler(final RowProcessor convert, final int columnIndex,
109            final String columnName) {
110        this.convert = convert;
111        this.columnIndex = columnIndex;
112        this.columnName = columnName;
113    }
114
115    /**
116     * Creates a new instance of KeyedHandler.
117     *
118     * @param columnName The values to use as keys in the Map are
119     * retrieved from the column with this name.
120     */
121    public KeyedHandler(final String columnName) {
122        this(ArrayHandler.ROW_PROCESSOR, 1, columnName);
123    }
124    /**
125     * This factory method is called by {@code handle()} to retrieve the
126     * key value from the current {@code ResultSet} row.  This
127     * implementation returns {@code ResultSet.getObject()} for the
128     * configured key column name or index.
129     * @param rs ResultSet to create a key from
130     * @return Object from the configured key column name/index
131     *
132     * @throws SQLException if a database access error occurs
133     * @throws ClassCastException if the class datatype does not match the column type
134     */
135    // We assume that the user has picked the correct type to match the column
136    // so getObject will return the appropriate type and the cast will succeed.
137    @SuppressWarnings("unchecked")
138    @Override
139    protected K createKey(final ResultSet rs) throws SQLException {
140        return columnName == null ?
141               (K) rs.getObject(columnIndex) :
142               (K) rs.getObject(columnName);
143    }
144
145    /**
146     * This factory method is called by {@code handle()} to store the
147     * current {@code ResultSet} row in some object. This
148     * implementation returns a {@code Map} with case insensitive column
149     * names as keys.  Calls to {@code map.get("COL")} and
150     * {@code map.get("col")} return the same value.
151     * @param resultSet ResultSet to create a row from
152     * @return Object typed Map containing column names to values
153     * @throws SQLException if a database access error occurs
154     */
155    @Override
156    protected Map<String, Object> createRow(final ResultSet resultSet) throws SQLException {
157        return this.convert.toMap(resultSet);
158    }
159
160}