/* * Copyright 2002-2005 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.springmodules.datamap.jdbc.sqlmap; import org.springframework.dao.DataAccessResourceFailureException; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.dao.DataAccessException; import org.springframework.dao.OptimisticLockingFailureException; import org.springframework.jdbc.core.PreparedStatementCreator; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.StatementCreatorUtils; import org.springframework.jdbc.core.support.JdbcDaoSupport; import org.springframework.jdbc.support.GeneratedKeyHolder; import org.springframework.jdbc.support.KeyHolder; import org.springmodules.datamap.dao.DataMapper; import org.springmodules.datamap.dao.Operators; import org.springmodules.datamap.jdbc.sqlmap.support.ActiveMapperUtils; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.math.BigDecimal; import java.util.*; import java.util.Date; import java.sql.*; /** * Implementation of DataMapper supporting metadata based default mappings to/from * database tables and columns to JavaBean compliant java classes. * * @author Thomas Risberg * @since 0.3 * @see org.springframework.jdbc.core.support.JdbcDaoSupport */ public class ActiveMapper extends JdbcDaoSupport implements DataMapper { private Class mappedClass; private PersistentObject persistentObject; private String tableNameToUse; private boolean overrideTableName = false; private String versionColumnToUse = "lock_version"; private boolean overrideVersionColumn = false; private Map pluralExceptions; private ActiveRowMapper rowMapper; public ActiveMapper() { } public ActiveMapper(Class clazz) { setMappedClass(clazz); } public void setMappedClass(Class clazz) throws DataAccessResourceFailureException { this.mappedClass = clazz; } public void setPluralExceptions(Map pluralExceptions) { this.pluralExceptions = pluralExceptions; } public Map getPluralExceptions() { return pluralExceptions; } public void setTableNameToUse(String tableNameToUse) { if (overrideTableName) throw new InvalidDataAccessApiUsageException("Table name can only be overriden once"); this.tableNameToUse = tableNameToUse; this.overrideTableName = true; } public String getTableNameToUse() { return tableNameToUse; } public void setVersionColumnToUse(String versionColumnToUse) { if (overrideVersionColumn) throw new InvalidDataAccessApiUsageException("Version column can only be overriden once"); this.versionColumnToUse = versionColumnToUse; this.overrideVersionColumn = true; } public String getVersionColumnToUse() { return versionColumnToUse; } protected PersistentObject getPersistentObject() { return persistentObject; } protected void initDao() throws Exception { if (pluralExceptions == null) { pluralExceptions = new HashMap(2); pluralExceptions.put("Address", "Addresses"); pluralExceptions.put("Country", "Countries"); pluralExceptions.put("Person", "People"); } persistentObject = ActiveMapperUtils.getPersistenceMetaData(mappedClass, getDataSource(), getPluralExceptions()); if (!overrideTableName) { tableNameToUse = persistentObject.getTableName(); } if (persistentObject.getPersistentFields().containsKey(getVersionColumnToUse())) { persistentObject.setVersioned(true); } rowMapper = new ActiveRowMapper(mappedClass, persistentObject); } public Object find(Object id) { if (persistentObject == null) throw new InvalidDataAccessApiUsageException("Persistent class not specified"); String sql = "select * from " + tableNameToUse + " where id = ?"; List l = getJdbcTemplate().query(sql, new Object[] {id}, rowMapper); if (l.size() > 0) return l.get(0); else return null; } public List findAll() { if (persistentObject == null) throw new InvalidDataAccessApiUsageException("Persistent class not specified"); String sql = "select * from " + tableNameToUse; return getJdbcTemplate().query(sql, rowMapper); } protected List findByWhereClause(String whereClause, Object[] arguments) { if (persistentObject == null) throw new InvalidDataAccessApiUsageException("Persistent class not specified"); String sql = "select * from " + tableNameToUse + " where " + whereClause; return getJdbcTemplate().query(sql, arguments, rowMapper); } public List findBy(String field, int operator, Object argument) { if (persistentObject == null) throw new InvalidDataAccessApiUsageException("Persistent class not specified"); StringBuffer whereClause = new StringBuffer(); StringBuffer endClause = new StringBuffer(); switch (operator) { case Operators.EQUALS: whereClause.append(ActiveMapperUtils.underscoreName(field)).append(" = "); break; case Operators.LESS_THAN: whereClause.append(ActiveMapperUtils.underscoreName(field)).append(" < "); break; case Operators.GREATER_THAN: whereClause.append(ActiveMapperUtils.underscoreName(field)).append(" > "); break; case Operators.LESS_THAN_OR_EQUAL: whereClause.append(ActiveMapperUtils.underscoreName(field)).append(" <= "); break; case Operators.GREATER_THAN_OR_EQUAL: whereClause.append(ActiveMapperUtils.underscoreName(field)).append(" >= "); break; case Operators.BETWEEN: whereClause.append(ActiveMapperUtils.underscoreName(field)).append(" between "); break; case Operators.IN: whereClause.append(ActiveMapperUtils.underscoreName(field)).append(" in ("); break; case Operators.STARTS_WITH: whereClause.append(ActiveMapperUtils.underscoreName(field)).append(" LIKE "); if (argument instanceof String) { argument = argument + "%"; } break; case Operators.ENDS_WITH: whereClause.append(ActiveMapperUtils.underscoreName(field)).append(" LIKE "); if (argument instanceof String) { argument = "%" + argument; } break; case Operators.CONTAINS: whereClause.append(ActiveMapperUtils.underscoreName(field)).append(" LIKE "); if (argument instanceof String) { argument = "%" + argument + "%"; } break; } Object[] arguments; if (argument instanceof Object[]) { arguments = (Object[])argument; for (int i = 0; i < arguments.length; i++) { if (i > 0) whereClause.append(", "); whereClause.append("?"); } endClause.append(")"); } else { arguments = new Object[] {argument}; whereClause.append("?"); } whereClause.append(endClause); return findByWhereClause(whereClause.toString(), arguments); } protected Object completeMappingOnFind(Object result, ResultSet rs, int rowNumber, Map mappedColumns, Map unmappedColumns) throws SQLException { return result; } public void save(Object o) throws DataAccessException { Map mappedFields = new HashMap(10); Map unmappedFields = new HashMap(10); List columnValues = new LinkedList(); List idValues = new LinkedList(); Set fieldSet = persistentObject.getPersistentFields().entrySet(); Number oldVersionValue = null; Number newVersionValue = null; boolean isNewRow = false; for (Iterator i = fieldSet.iterator(); i.hasNext();) { Map.Entry e = (Map.Entry)i.next(); PersistentField pf = (PersistentField)e.getValue(); if (pf.getSqlType() > 0) { mappedFields.put(pf.getFieldName(), pf); try { Method m = o.getClass().getMethod(ActiveMapperUtils.getterName(pf.getFieldName()), new Class[] {}); Object r = m.invoke(o, new Object[] {}); if (pf.isIdField()) { idValues.add(new PersistentValue(pf.getColumnName(), pf.getSqlType(), r)); if (r == null) { isNewRow = true; } } else { if (persistentObject.isVersioned() && getVersionColumnToUse().equals(pf.getColumnName())) { if (r == null || r instanceof Number) { if (r != null) { oldVersionValue = (Number)r; if (pf.getClass() == Integer.class) { newVersionValue = new Integer(oldVersionValue.intValue() + 1); } else { newVersionValue = new Long(oldVersionValue.longValue() + 1); } } else { oldVersionValue = null; if (pf.getClass() == Integer.class) { newVersionValue = new Integer(1); } else { newVersionValue = new Long(1); } } columnValues.add(new PersistentValue(pf.getColumnName(), pf.getSqlType(), newVersionValue)); } else { throw new InvalidDataAccessApiUsageException("Invalid type for version column. Must be of type Number."); } } else { columnValues.add(new PersistentValue(pf.getColumnName(), pf.getSqlType(), r)); } } } catch (NoSuchMethodException e1) { throw new DataAccessResourceFailureException(new StringBuffer().append("Failed to map field ").append(pf.getFieldName()).append(".").toString(), e1); } catch (IllegalAccessException e1) { throw new DataAccessResourceFailureException(new StringBuffer().append("Failed to map field ").append(pf.getFieldName()).append(".").toString(), e1); } catch (InvocationTargetException e1) { throw new DataAccessResourceFailureException(new StringBuffer().append("Failed to map field ").append(pf.getFieldName()).append(".").toString(), e1); } } else { unmappedFields.put(pf.getFieldName(), pf); } } List additionalColumnValues = completeMappingOnSave(o, mappedFields, unmappedFields); for (Iterator iter = additionalColumnValues.iterator(); iter.hasNext(); ) { Object colVal = iter.next(); if (colVal instanceof PersistentValue) { if (((PersistentValue)colVal).isIdValue()) idValues.add(colVal); else columnValues.add(colVal); } else { throw new InvalidDataAccessApiUsageException("Invalid type returned for additional columns. Must be of type PersistentValue."); } } if (persistentObject.isDependentObject()) { StringBuffer cntSql = new StringBuffer(); cntSql.append("select count(*) from ").append(tableNameToUse); cntSql.append(" where "); for (int i = 0; i < idValues.size(); i++) { if (i > 0) cntSql.append(" and "); cntSql.append(((PersistentValue) idValues.get(i)).getColumnName()).append(" = ?"); } Object[] keyValues = new Object[idValues.size()]; for (int i = 0; i < idValues.size(); i++) { keyValues[i] = ((PersistentValue)idValues.get(i)).getValue(); } int rowCount = getJdbcTemplate().queryForInt(cntSql.toString(), keyValues); if (rowCount == 0) { isNewRow = true; } } StringBuffer sql = new StringBuffer(); final List parameterValues = new LinkedList(); if (!isNewRow) { sql.append("update ").append(tableNameToUse).append(" set "); for (int i = 0; i < columnValues.size(); i++) { if (i > 0) sql.append(", "); sql.append(((PersistentValue)columnValues.get(i)).getColumnName()); sql.append(" = ?"); parameterValues.add(columnValues.get(i)); } sql.append(" where "); for (int i = 0; i < idValues.size(); i++) { if (i > 0) sql.append(" and "); sql.append(((PersistentValue) idValues.get(i)).getColumnName()).append(" = ?"); parameterValues.add(idValues.get(i)); } if (persistentObject.isVersioned() && oldVersionValue != null) { sql.append(" and "); sql.append(((PersistentField)persistentObject.getPersistentFields().get(getVersionColumnToUse())).getColumnName()).append(" = ?"); parameterValues.add(new PersistentValue(((PersistentField)persistentObject.getPersistentFields().get(getVersionColumnToUse())).getColumnName(), Types.NUMERIC, oldVersionValue)); } } else { StringBuffer placeholders = new StringBuffer(); sql.append("insert into "); sql.append(tableNameToUse); sql.append(" ("); if (persistentObject.isDependentObject() || !persistentObject.isUsingGeneratedKeysStrategy()) { if (persistentObject.isDependentObject()) { for (int i = 0; i < idValues.size(); i++) { if (i > 0) sql.append(", "); sql.append(((PersistentValue)idValues.get(i)).getColumnName()); if (i > 0) placeholders.append(", "); placeholders.append("?"); parameterValues.add(idValues.get(i)); } } else { sql.append(((PersistentValue)idValues.get(0)).getColumnName()); ((PersistentValue)idValues.get(0)).setValue(assignNewId(o)); placeholders.append("?"); parameterValues.add(idValues.get(0)); } } for (int i = 0; i < columnValues.size(); i++) { if (i > 0 || (!persistentObject.isUsingGeneratedKeysStrategy())) { sql.append(", "); placeholders.append(", "); } sql.append(((PersistentValue)columnValues.get(i)).getColumnName()); placeholders.append("?"); parameterValues.add(columnValues.get(i)); } sql.append(") values("); sql.append(placeholders); sql.append(")"); } logger.debug("SQL for Save: " + sql); int updateCount = 0; if (isNewRow && persistentObject.isUsingGeneratedKeysStrategy()) { KeyHolder keyHolder = new GeneratedKeyHolder(); final String prepSql = sql.toString(); updateCount = getJdbcTemplate().update(new PreparedStatementCreator() { public PreparedStatement createPreparedStatement(Connection con) throws SQLException { PreparedStatement ps = con.prepareStatement(prepSql, Statement.RETURN_GENERATED_KEYS); int cnt = 0; for (Iterator iter = parameterValues.iterator(); iter.hasNext(); ) { PersistentValue pv = (PersistentValue)iter.next(); StatementCreatorUtils.setParameterValue(ps, cnt+1, pv.getSqlType(), null, pv.getValue()); cnt++; } return ps; } }, keyHolder); assignGeneratedKey(o, keyHolder); } else { final String prepSql = sql.toString(); updateCount = getJdbcTemplate().update(new PreparedStatementCreator() { public PreparedStatement createPreparedStatement(Connection con) throws SQLException { PreparedStatement ps = con.prepareStatement(prepSql); int cnt = 0; for (Iterator iter = parameterValues.iterator(); iter.hasNext(); ) { PersistentValue pv = (PersistentValue)iter.next(); StatementCreatorUtils.setParameterValue(ps, cnt+1, pv.getSqlType(), null, pv.getValue()); cnt++; } return ps; } }); } if (persistentObject.isVersioned()) { if (updateCount > 0) { assignNewVersion(o, getVersionColumnToUse(), newVersionValue); } else { throw new OptimisticLockingFailureException("Versioning error for " + o.getClass().getName() + " with " + ((PersistentField)persistentObject.getPersistentFields().get(getVersionColumnToUse())).getFieldName() + " " + oldVersionValue + " and key(s) " + idValues); } } } protected List completeMappingOnSave(Object input, Map mappedFields, Map unmappedFields) { return new ArrayList(0); } public void delete(Object o) { String sql = "delete from " + tableNameToUse + " where id = ?"; PersistentField pf = (PersistentField)persistentObject.getPersistentFields().get("id"); Object[] idValue = new Object[1]; try { Method m = o.getClass().getMethod(ActiveMapperUtils.getterName(pf.getFieldName()), new Class[] {}); idValue[0] = m.invoke(o, new Object[] {}); } catch (NoSuchMethodException e1) { throw new DataAccessResourceFailureException(new StringBuffer().append("Failed to map field ").append(pf.getFieldName()).append(".").toString(), e1); } catch (IllegalAccessException e1) { throw new DataAccessResourceFailureException(new StringBuffer().append("Failed to map field ").append(pf.getFieldName()).append(".").toString(), e1); } catch (InvocationTargetException e1) { throw new DataAccessResourceFailureException(new StringBuffer().append("Failed to map field ").append(pf.getFieldName()).append(".").toString(), e1); } getJdbcTemplate().update(sql, idValue); } protected Object assignNewId(Object o) { PersistentField pf = (PersistentField)persistentObject.getPersistentFields().get("id"); Object newId = null; if (pf.getJavaType() == Long.class) { newId = new Long(persistentObject.getIncrementer().nextLongValue()); } else if (pf.getJavaType() == Integer.class) { newId = new Integer(persistentObject.getIncrementer().nextIntValue()); } try { Method m = o.getClass().getMethod(ActiveMapperUtils.setterName(pf.getFieldName()), new Class[] {newId.getClass()}); m.invoke(o, new Object[] {newId}); } catch (NoSuchMethodException e1) { e1.printStackTrace(); } catch (IllegalAccessException e1) { e1.printStackTrace(); } catch (InvocationTargetException e1) { e1.printStackTrace(); } return newId; } protected Object assignGeneratedKey(Object o, KeyHolder kh) { PersistentField pf = (PersistentField)persistentObject.getPersistentFields().get("id"); Object newId = kh.getKey(); try { Method m = o.getClass().getMethod(ActiveMapperUtils.setterName(pf.getFieldName()), new Class[] {newId.getClass()}); m.invoke(o, new Object[] {newId}); } catch (NoSuchMethodException e1) { e1.printStackTrace(); } catch (IllegalAccessException e1) { e1.printStackTrace(); } catch (InvocationTargetException e1) { e1.printStackTrace(); } return newId; } protected Object assignNewVersion(Object o, String columnName, Object newVersionNumber) { PersistentField pf = (PersistentField)persistentObject.getPersistentFields().get(columnName); try { Method m = o.getClass().getMethod(ActiveMapperUtils.setterName(pf.getFieldName()), new Class[] {newVersionNumber.getClass()}); m.invoke(o, new Object[] {newVersionNumber}); } catch (NoSuchMethodException e1) { e1.printStackTrace(); } catch (IllegalAccessException e1) { e1.printStackTrace(); } catch (InvocationTargetException e1) { e1.printStackTrace(); } return o; } private class ActiveRowMapper implements RowMapper { private Class mappedClass = null; private PersistentObject persistentObject = null; private Constructor defaultConstruct; public ActiveRowMapper(Class clazz, PersistentObject persistentObject) { this.mappedClass = clazz; this.persistentObject = persistentObject; try { defaultConstruct = mappedClass.getConstructor(null); } catch (NoSuchMethodException e) { throw new DataAccessResourceFailureException(new StringBuffer().append("Failed to access default constructor of ").append(mappedClass.getName()).toString(), e); } } public Object mapRow(ResultSet rs, int rowNumber) throws SQLException { Object result; try { result = defaultConstruct.newInstance(null); } catch (IllegalAccessException e) { throw new DataAccessResourceFailureException("Failed to load class " + mappedClass.getName(), e); } catch (InvocationTargetException e) { throw new DataAccessResourceFailureException("Failed to load class " + mappedClass.getName(), e); } catch (InstantiationException e) { throw new DataAccessResourceFailureException("Failed to load class " + mappedClass.getName(), e); } ResultSetMetaData meta = rs.getMetaData(); int columns = meta.getColumnCount(); Map mappedColumns = new HashMap(10); Map unmappedColumns = new HashMap(10); for (int x = 1; x <= columns; x++) { String field = meta.getColumnName(x).toLowerCase(); PersistentField fieldMeta = (PersistentField)persistentObject.getPersistentFields().get(field); if (fieldMeta != null) { Object value = null; Method m = null; try { if (fieldMeta.getJavaType().equals(String.class)) { m = result.getClass().getMethod(ActiveMapperUtils.setterName(fieldMeta.getColumnName()), new Class[] {String.class}); value = rs.getString(x); } else if (fieldMeta.getJavaType().equals(Byte.class)) { m = result.getClass().getMethod(ActiveMapperUtils.setterName(fieldMeta.getColumnName()), new Class[] {Byte.class}); value = new Byte(rs.getByte(x)); } else if (fieldMeta.getJavaType().equals(Short.class)) { m = result.getClass().getMethod(ActiveMapperUtils.setterName(fieldMeta.getColumnName()), new Class[] {Short.class}); value = new Short(rs.getShort(x)); } else if (fieldMeta.getJavaType().equals(Integer.class)) { m = result.getClass().getMethod(ActiveMapperUtils.setterName(fieldMeta.getColumnName()), new Class[] {Integer.class}); value = new Integer(rs.getInt(x)); } else if (fieldMeta.getJavaType().equals(Long.class)) { m = result.getClass().getMethod(ActiveMapperUtils.setterName(fieldMeta.getColumnName()), new Class[] {Long.class}); value = new Long(rs.getLong(x)); } else if (fieldMeta.getJavaType().equals(Float.class)) { m = result.getClass().getMethod(ActiveMapperUtils.setterName(fieldMeta.getColumnName()), new Class[] {Float.class}); value = new Float(rs.getFloat(x)); } else if (fieldMeta.getJavaType().equals(Double.class)) { m = result.getClass().getMethod(ActiveMapperUtils.setterName(fieldMeta.getColumnName()), new Class[] {Double.class}); value = new Double(rs.getDouble(x)); } else if (fieldMeta.getJavaType().equals(BigDecimal.class)) { m = result.getClass().getMethod(ActiveMapperUtils.setterName(fieldMeta.getColumnName()), new Class[] {BigDecimal.class}); value = rs.getBigDecimal(x); } else if (fieldMeta.getJavaType().equals(Boolean.class)) { m = result.getClass().getMethod(ActiveMapperUtils.setterName(fieldMeta.getColumnName()), new Class[] {Boolean.class}); value = (rs.getBoolean(x)) ? Boolean.TRUE : Boolean.FALSE; } else if (fieldMeta.getJavaType().equals(Date.class)) { m = result.getClass().getMethod(ActiveMapperUtils.setterName(fieldMeta.getColumnName()), new Class[] {Date.class}); if (fieldMeta.getSqlType() == Types.DATE) { value = rs.getDate(x); } else if (fieldMeta.getSqlType() == Types.TIME) { value = rs.getTime(x); } else { value = rs.getTimestamp(x); } } else { unmappedColumns.put(fieldMeta.getColumnName(), fieldMeta); } if (m != null) { m.invoke(result , new Object[] {value}); mappedColumns.put(meta.getColumnName(x), meta); } } 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); } } else { unmappedColumns.put(meta.getColumnName(x), meta); } } return completeMappingOnFind(result, rs, rowNumber, mappedColumns, unmappedColumns); } } }