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