001: package liquibase.database.template;
002:
003: import liquibase.util.JdbcUtils;
004: import liquibase.util.NumberUtils;
005:
006: import java.math.BigDecimal;
007: import java.sql.*;
008:
009: /**
010: * RowMapper implementation that converts a single column into
011: * a single result value per row. Expects to work on a ResultSet
012: * that just contains a single column.
013: * <p/>
014: * <p>The type of the result value for each row can be specified.
015: * The value for the single column will be extracted from the ResultSet
016: * and converted into the specified target type.
017: *
018: * @author Spring Framework
019: */
020: class SingleColumnRowMapper implements RowMapper {
021:
022: private Class requiredType;
023:
024: /**
025: * Create a new SingleColumnRowMapper.
026: *
027: * @see #setRequiredType
028: */
029: public SingleColumnRowMapper() {
030: }
031:
032: /**
033: * Create a new SingleColumnRowMapper.
034: *
035: * @param requiredType the type that each result object is expected to match
036: */
037: public SingleColumnRowMapper(Class requiredType) {
038: this .requiredType = requiredType;
039: }
040:
041: /**
042: * Set the type that each result object is expected to match.
043: * <p>If not specified, the column value will be exposed as
044: * returned by the JDBC driver.
045: */
046: public void setRequiredType(Class requiredType) {
047: this .requiredType = requiredType;
048: }
049:
050: /**
051: * Extract a value for the single column in the current row.
052: * <p>Validates that there is only one column selected,
053: * then delegates to <code>getColumnValue()</code> and also
054: * <code>convertValueToRequiredType</code>, if necessary.
055: *
056: * @see java.sql.ResultSetMetaData#getColumnCount()
057: * @see #getColumnValue(java.sql.ResultSet,int,Class)
058: * @see #convertValueToRequiredType(Object,Class)
059: */
060: public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
061: // Validate column count.
062: ResultSetMetaData rsmd = rs.getMetaData();
063: int nrOfColumns = rsmd.getColumnCount();
064: if (nrOfColumns != 1) {
065: throw new SQLException("Returned too many rows");
066: }
067:
068: // Extract column value from JDBC ResultSet
069: Object result = getColumnValue(rs, 1, this .requiredType);
070: if (result != null && this .requiredType != null
071: && !this .requiredType.isInstance(result)) {
072: // Extracted value does not match already: try to convert it.
073: try {
074: return convertValueToRequiredType(result,
075: this .requiredType);
076: } catch (IllegalArgumentException ex) {
077: throw new SQLException(
078: "Type mismatch affecting row number " + rowNum
079: + " and column type '"
080: + rsmd.getColumnTypeName(1) + "': "
081: + ex.getMessage());
082: }
083: }
084: return result;
085: }
086:
087: /**
088: * Retrieve a JDBC object value for the specified column.
089: * <p>The default implementation calls <code>ResultSet.getString(index)</code> etc
090: * for all standard value types (String, Boolean, number types, date types, etc).
091: * It calls <code>ResultSet.getObject(index)</code> else.
092: * <p>If no required type has been specified, this method delegates to
093: * <code>getColumnValue(rs, index)</code>, which basically calls
094: * <code>ResultSet.getObject(index)</code> but applies some additional
095: * default conversion to appropriate value types.
096: * <p>Explicit extraction of a String is necessary to properly extract an Oracle
097: * RAW value as a String, for example. For the other given types, it is also
098: * recommendable to extract the desired types explicitly, to let the JDBC driver
099: * perform appropriate (potentially database-specific) conversion.
100: *
101: * @param rs is the ResultSet holding the data
102: * @param index is the column index
103: * @param requiredType the type that each result object is expected to match
104: * (or <code>null</code> if none specified)
105: * @return the Object value
106: * @see java.sql.ResultSet#getString(int)
107: * @see java.sql.ResultSet#getObject(int)
108: * @see #getColumnValue(java.sql.ResultSet,int)
109: */
110: protected Object getColumnValue(ResultSet rs, int index,
111: Class requiredType) throws SQLException {
112: if (requiredType != null) {
113: Object value;
114: boolean wasNullCheck = false;
115:
116: // Explicitly extract typed value, as far as possible.
117: if (String.class.equals(requiredType)) {
118: value = rs.getString(index);
119: } else if (Boolean.class.equals(requiredType)) {
120: value = (rs.getBoolean(index) ? Boolean.TRUE
121: : Boolean.FALSE);
122: wasNullCheck = true;
123: } else if (Byte.class.equals(requiredType)) {
124: value = rs.getByte(index);
125: wasNullCheck = true;
126: } else if (Short.class.equals(requiredType)) {
127: value = rs.getShort(index);
128: wasNullCheck = true;
129: } else if (Integer.class.equals(requiredType)) {
130: value = rs.getInt(index);
131: wasNullCheck = true;
132: } else if (Long.class.equals(requiredType)) {
133: value = rs.getLong(index);
134: wasNullCheck = true;
135: } else if (Float.class.equals(requiredType)) {
136: value = rs.getFloat(index);
137: wasNullCheck = true;
138: } else if (Double.class.equals(requiredType)
139: || Number.class.equals(requiredType)) {
140: value = rs.getDouble(index);
141: wasNullCheck = true;
142: } else if (byte[].class.equals(requiredType)) {
143: value = rs.getBytes(index);
144: } else if (java.sql.Date.class.equals(requiredType)) {
145: value = rs.getDate(index);
146: } else if (java.sql.Time.class.equals(requiredType)) {
147: value = rs.getTime(index);
148: } else if (java.sql.Timestamp.class.equals(requiredType)
149: || java.util.Date.class.equals(requiredType)) {
150: value = rs.getTimestamp(index);
151: } else if (BigDecimal.class.equals(requiredType)) {
152: value = rs.getBigDecimal(index);
153: } else if (Blob.class.equals(requiredType)) {
154: value = rs.getBlob(index);
155: } else if (Clob.class.equals(requiredType)) {
156: value = rs.getClob(index);
157: } else {
158: // Some unknown type desired -> rely on getObject.
159: value = rs.getObject(index);
160: }
161:
162: // Perform was-null check if demanded (for results that the
163: // JDBC driver returns as primitives).
164: if (wasNullCheck && value != null && rs.wasNull()) {
165: value = null;
166: }
167: return value;
168: } else {
169: // No required type specified -> perform default extraction.
170: return getColumnValue(rs, index);
171: }
172: }
173:
174: /**
175: * Retrieve a JDBC object value for the specified column, using the most
176: * appropriate value type. Called if no required type has been specified.
177: * <p>The default implementation delegates to <code>JdbcUtils.getResultSetValue()</code>,
178: * which uses the <code>ResultSet.getObject(index)</code> method. Additionally,
179: * it includes a "hack" to get around Oracle returning a non-standard object for
180: * their TIMESTAMP datatype. See the <code>JdbcUtils#getResultSetValue()</code>
181: * javadoc for details.
182: *
183: * @param rs is the ResultSet holding the data
184: * @param index is the column index
185: * @return the Object value
186: */
187: protected Object getColumnValue(ResultSet rs, int index)
188: throws SQLException {
189: return JdbcUtils.getResultSetValue(rs, index);
190: }
191:
192: /**
193: * Convert the given column value to the specified required type.
194: * Only called if the extracted column value does not match already.
195: * <p>If the required type is String, the value will simply get stringified
196: * via <code>toString()</code>. In case of a Number, the value will be
197: * converted into a Number, either through number conversion or through
198: * String parsing (depending on the value type).
199: *
200: * @param value the column value as extracted from <code>getColumnValue()</code>
201: * (never <code>null</code>)
202: * @param requiredType the type that each result object is expected to match
203: * (never <code>null</code>)
204: * @return the converted value
205: * @see #getColumnValue(java.sql.ResultSet,int,Class)
206: */
207: protected Object convertValueToRequiredType(Object value,
208: Class requiredType) {
209: if (String.class.equals(this .requiredType)) {
210: return value.toString();
211: } else if (Number.class.isAssignableFrom(this .requiredType)) {
212: if (value instanceof Number) {
213: // Convert original Number to target Number class.
214: return NumberUtils.convertNumberToTargetClass(
215: ((Number) value), this .requiredType);
216: } else {
217: // Convert stringified value to target Number class.
218: return NumberUtils.parseNumber(value.toString(),
219: this .requiredType);
220: }
221: } else {
222: throw new IllegalArgumentException("Value [" + value
223: + "] is of type [" + value.getClass().getName()
224: + "] and cannot be converted to required type ["
225: + this .requiredType.getName() + "]");
226: }
227: }
228: }
|