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;
018
019 import java.sql.ResultSet;
020 import java.sql.ResultSetMetaData;
021 import java.sql.SQLException;
022 import java.util.HashMap;
023 import java.util.List;
024 import java.util.Locale;
025 import java.util.Map;
026
027 /**
028 * Basic implementation of the <code>RowProcessor</code> interface.
029 *
030 * <p>
031 * This class is thread-safe.
032 * </p>
033 *
034 * @see RowProcessor
035 */
036 public class BasicRowProcessor implements RowProcessor {
037
038 /**
039 * The default BeanProcessor instance to use if not supplied in the
040 * constructor.
041 */
042 private static final BeanProcessor defaultConvert = new BeanProcessor();
043
044 /**
045 * The Singleton instance of this class.
046 */
047 private static final BasicRowProcessor instance = new BasicRowProcessor();
048
049 /**
050 * Returns the Singleton instance of this class.
051 *
052 * @return The single instance of this class.
053 * @deprecated Create instances with the constructors instead. This will
054 * be removed after DbUtils 1.1.
055 */
056 @Deprecated
057 public static BasicRowProcessor instance() {
058 return instance;
059 }
060
061 /**
062 * Use this to process beans.
063 */
064 private final BeanProcessor convert;
065
066 /**
067 * BasicRowProcessor constructor. Bean processing defaults to a
068 * BeanProcessor instance.
069 */
070 public BasicRowProcessor() {
071 this(defaultConvert);
072 }
073
074 /**
075 * BasicRowProcessor constructor.
076 * @param convert The BeanProcessor to use when converting columns to
077 * bean properties.
078 * @since DbUtils 1.1
079 */
080 public BasicRowProcessor(BeanProcessor convert) {
081 super();
082 this.convert = convert;
083 }
084
085 /**
086 * Convert a <code>ResultSet</code> row into an <code>Object[]</code>.
087 * This implementation copies column values into the array in the same
088 * order they're returned from the <code>ResultSet</code>. Array elements
089 * will be set to <code>null</code> if the column was SQL NULL.
090 *
091 * @see org.apache.commons.dbutils.RowProcessor#toArray(java.sql.ResultSet)
092 * @param rs ResultSet that supplies the array data
093 * @throws SQLException if a database access error occurs
094 * @return the newly created array
095 */
096 @Override
097 public Object[] toArray(ResultSet rs) throws SQLException {
098 ResultSetMetaData meta = rs.getMetaData();
099 int cols = meta.getColumnCount();
100 Object[] result = new Object[cols];
101
102 for (int i = 0; i < cols; i++) {
103 result[i] = rs.getObject(i + 1);
104 }
105
106 return result;
107 }
108
109 /**
110 * Convert a <code>ResultSet</code> row into a JavaBean. This
111 * implementation delegates to a BeanProcessor instance.
112 * @see org.apache.commons.dbutils.RowProcessor#toBean(java.sql.ResultSet, java.lang.Class)
113 * @see org.apache.commons.dbutils.BeanProcessor#toBean(java.sql.ResultSet, java.lang.Class)
114 * @param <T> The type of bean to create
115 * @param rs ResultSet that supplies the bean data
116 * @param type Class from which to create the bean instance
117 * @throws SQLException if a database access error occurs
118 * @return the newly created bean
119 */
120 @Override
121 public <T> T toBean(ResultSet rs, Class<T> type) throws SQLException {
122 return this.convert.toBean(rs, type);
123 }
124
125 /**
126 * Convert a <code>ResultSet</code> into a <code>List</code> of JavaBeans.
127 * This implementation delegates to a BeanProcessor instance.
128 * @see org.apache.commons.dbutils.RowProcessor#toBeanList(java.sql.ResultSet, java.lang.Class)
129 * @see org.apache.commons.dbutils.BeanProcessor#toBeanList(java.sql.ResultSet, java.lang.Class)
130 * @param <T> The type of bean to create
131 * @param rs ResultSet that supplies the bean data
132 * @param type Class from which to create the bean instance
133 * @throws SQLException if a database access error occurs
134 * @return A <code>List</code> of beans with the given type in the order
135 * they were returned by the <code>ResultSet</code>.
136 */
137 @Override
138 public <T> List<T> toBeanList(ResultSet rs, Class<T> type) throws SQLException {
139 return this.convert.toBeanList(rs, type);
140 }
141
142 /**
143 * Convert a <code>ResultSet</code> row into a <code>Map</code>. This
144 * implementation returns a <code>Map</code> with case insensitive column
145 * names as keys. Calls to <code>map.get("COL")</code> and
146 * <code>map.get("col")</code> return the same value.
147 * @see org.apache.commons.dbutils.RowProcessor#toMap(java.sql.ResultSet)
148 * @param rs ResultSet that supplies the map data
149 * @throws SQLException if a database access error occurs
150 * @return the newly created Map
151 */
152 @Override
153 public Map<String, Object> toMap(ResultSet rs) throws SQLException {
154 Map<String, Object> result = new CaseInsensitiveHashMap();
155 ResultSetMetaData rsmd = rs.getMetaData();
156 int cols = rsmd.getColumnCount();
157
158 for (int i = 1; i <= cols; i++) {
159 result.put(rsmd.getColumnName(i), rs.getObject(i));
160 }
161
162 return result;
163 }
164
165 /**
166 * A Map that converts all keys to lowercase Strings for case insensitive
167 * lookups. This is needed for the toMap() implementation because
168 * databases don't consistently handle the casing of column names.
169 *
170 * <p>The keys are stored as they are given [BUG #DBUTILS-34], so we maintain
171 * an internal mapping from lowercase keys to the real keys in order to
172 * achieve the case insensitive lookup.
173 *
174 * <p>Note: This implementation does not allow <tt>null</tt>
175 * for key, whereas {@link HashMap} does, because of the code:
176 * <pre>
177 * key.toString().toLowerCase()
178 * </pre>
179 */
180 private static class CaseInsensitiveHashMap extends HashMap<String, Object> {
181 /**
182 * The internal mapping from lowercase keys to the real keys.
183 *
184 * <p>
185 * Any query operation using the key
186 * ({@link #get(Object)}, {@link #containsKey(Object)})
187 * is done in three steps:
188 * <ul>
189 * <li>convert the parameter key to lower case</li>
190 * <li>get the actual key that corresponds to the lower case key</li>
191 * <li>query the map with the actual key</li>
192 * </ul>
193 * </p>
194 */
195 private final Map<String, String> lowerCaseMap = new HashMap<String, String>();
196
197 /**
198 * Required for serialization support.
199 *
200 * @see java.io.Serializable
201 */
202 private static final long serialVersionUID = -2848100435296897392L;
203
204 /** {@inheritDoc} */
205 @Override
206 public boolean containsKey(Object key) {
207 Object realKey = lowerCaseMap.get(key.toString().toLowerCase(Locale.ENGLISH));
208 return super.containsKey(realKey);
209 // Possible optimisation here:
210 // Since the lowerCaseMap contains a mapping for all the keys,
211 // we could just do this:
212 // return lowerCaseMap.containsKey(key.toString().toLowerCase());
213 }
214
215 /** {@inheritDoc} */
216 @Override
217 public Object get(Object key) {
218 Object realKey = lowerCaseMap.get(key.toString().toLowerCase(Locale.ENGLISH));
219 return super.get(realKey);
220 }
221
222 /** {@inheritDoc} */
223 @Override
224 public Object put(String key, Object value) {
225 /*
226 * In order to keep the map and lowerCaseMap synchronized,
227 * we have to remove the old mapping before putting the
228 * new one. Indeed, oldKey and key are not necessaliry equals.
229 * (That's why we call super.remove(oldKey) and not just
230 * super.put(key, value))
231 */
232 Object oldKey = lowerCaseMap.put(key.toLowerCase(Locale.ENGLISH), key);
233 Object oldValue = super.remove(oldKey);
234 super.put(key, value);
235 return oldValue;
236 }
237
238 /** {@inheritDoc} */
239 @Override
240 public void putAll(Map<? extends String, ?> m) {
241 for (Map.Entry<? extends String, ?> entry : m.entrySet()) {
242 String key = entry.getKey();
243 Object value = entry.getValue();
244 this.put(key, value);
245 }
246 }
247
248 /** {@inheritDoc} */
249 @Override
250 public Object remove(Object key) {
251 Object realKey = lowerCaseMap.remove(key.toString().toLowerCase(Locale.ENGLISH));
252 return super.remove(realKey);
253 }
254 }
255
256 }