/* * Copyright 2002-2006 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.jdbc.core; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.math.BigDecimal; import java.sql.Date; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.sql.Types; import java.util.HashMap; import java.util.Map; import java.util.StringTokenizer; import org.springframework.dao.DataAccessResourceFailureException; /** * RowMapper implementation that converts a row into a new instance * of the specified mapped class. Column values are mapped based on matching * the column name as obtained from result set metadata to public setters for * the corresponding properties. The names are matched either directly or by transforming * a name separating the parts with underscores to the same name using "camel" case. * * @author Thomas Risberg * @since 2.0 */ public class ActiveRowMapper implements RowMapper { private Class mappedClass; private Constructor defaultConstruct; private Map mappedFields; /** * Create a new ActiveRowMapper. * @see #setMappedClass */ public ActiveRowMapper() { } /** * Create a new ActiveRowMapper. * @param mappedClass the class that each row should be mapped to. */ public ActiveRowMapper(Class mappedClass) { setMappedClass(mappedClass); } /** * Set the class that each row should be mapped to. */ public void setMappedClass(Class mappedClass) { this.mappedClass = mappedClass; try { this.defaultConstruct = mappedClass.getConstructor((Class[]) null); } catch (NoSuchMethodException ex) { throw new DataAccessResourceFailureException(new StringBuffer().append("Failed to access default constructor of ").append(mappedClass.getName()).toString(), ex); } this.mappedFields = new HashMap(); Field[] f = mappedClass.getDeclaredFields(); for (int i = 0; i < f.length; i++) { PersistentField pf = new PersistentField(); pf.setFieldName(f[i].getName()); pf.setColumnName(underscoreName(f[i].getName())); pf.setJavaType(f[i].getType()); this.mappedFields.put(pf.getColumnName(), pf); } } /** * Extract the values for all columns in the current row. * <p>Utilizes public setters and result set metadata. * @see java.sql.ResultSetMetaData */ public Object mapRow(ResultSet rs, int rowNumber) throws SQLException { Object result; try { result = this.defaultConstruct.newInstance((Object[]) null); } catch (IllegalAccessException e) { throw new DataAccessResourceFailureException("Failed to load class " + this.mappedClass.getName(), e); } catch (InvocationTargetException e) { throw new DataAccessResourceFailureException("Failed to load class " + this.mappedClass.getName(), e); } catch (InstantiationException e) { throw new DataAccessResourceFailureException("Failed to load class " + this.mappedClass.getName(), e); } ResultSetMetaData meta = rs.getMetaData(); int columns = meta.getColumnCount(); for (int i = 1; i <= columns; i++) { String field = meta.getColumnName(i).toLowerCase(); PersistentField fieldMeta = (PersistentField) this.mappedFields.get(field); if (fieldMeta != null) { try { Object value = null; Class fieldType = fieldMeta.getJavaType(); Method m = result.getClass().getMethod(setterName(fieldMeta.getColumnName()), new Class[]{fieldType}); if (fieldType.equals(String.class)) { value = rs.getString(field); } else if (fieldType.equals(byte.class) || fieldType.equals(Byte.class)) { value = new Byte(rs.getByte(field)); } else if (fieldType.equals(short.class) || fieldType.equals(Short.class)) { value = new Short(rs.getShort(field)); } else if (fieldType.equals(int.class) || fieldType.equals(Integer.class)) { value = new Integer(rs.getInt(field)); } else if (fieldType.equals(long.class) || fieldType.equals(Long.class)) { value = new Long(rs.getLong(field)); } else if (fieldType.equals(float.class) || fieldType.equals(Float.class)) { value = new Float(rs.getFloat(field)); } else if (fieldType.equals(double.class) || fieldType.equals(Double.class)) { value = new Double(rs.getDouble(field)); } else if (fieldType.equals(BigDecimal.class)) { value = rs.getBigDecimal(field); } else if (fieldType.equals(boolean.class) || fieldType.equals(Boolean.class)) { value = (rs.getBoolean(field)) ? Boolean.TRUE : Boolean.FALSE; } else if (fieldType.equals(Date.class)) { if (fieldMeta.getSqlType() == Types.DATE) { value = rs.getDate(field); } else if (fieldMeta.getSqlType() == Types.TIME) { value = rs.getTime(field); } else { value = rs.getTimestamp(field); } } if (m != null) { m.invoke(result, new Object[]{value}); } } catch (NoSuchMethodException e) { throw new DataAccessResourceFailureException(new StringBuffer().append("Failed to map field ").append(fieldMeta.getFieldName()).append(".").toString(), e); } catch (IllegalAccessException e) { throw new DataAccessResourceFailureException(new StringBuffer().append("Failed to map field ").append(fieldMeta.getFieldName()).append(".").toString(), e); } catch (InvocationTargetException e) { throw new DataAccessResourceFailureException(new StringBuffer().append("Failed to map field ").append(fieldMeta.getFieldName()).append(".").toString(), e); } } } return result; } public static String underscoreName(String name) { // This is a 1.4 and later method - do we need to support 1.3? return name.substring(0, 1).toLowerCase() + name.substring(1).replaceAll("([A-Z])", "_$1").toLowerCase(); } private String setterName(String columnName) { StringTokenizer tokenizer = new StringTokenizer(columnName, "_"); StringBuffer propertyName = new StringBuffer(); while (tokenizer.hasMoreTokens()) { String token = tokenizer.nextToken(); propertyName.append(token.substring(0, 1).toUpperCase()); propertyName.append(token.substring(1)); } return "set" + propertyName.toString(); } private class PersistentField { private String fieldName; private String columnName; private Class javaType; private int sqlType; public String getFieldName() { return fieldName; } public void setFieldName(String fieldName) { this.fieldName = fieldName; } public String getColumnName() { return columnName; } public void setColumnName(String columnName) { this.columnName = columnName; } public Class getJavaType() { return javaType; } public void setJavaType(Class javaType) { this.javaType = javaType; } public int getSqlType() { return sqlType; } public void setSqlType(int sqlType) { this.sqlType = sqlType; } } }