001 package hirondelle.web4j.database;
002
003 import hirondelle.web4j.BuildImpl;
004 import hirondelle.web4j.model.ConvertParam;
005 import hirondelle.web4j.model.DateTime;
006 import hirondelle.web4j.model.Decimal;
007 import hirondelle.web4j.model.Id;
008 import hirondelle.web4j.security.SafeText;
009 import hirondelle.web4j.util.Util;
010
011 import java.math.BigDecimal;
012 import java.sql.Clob;
013 import java.sql.ResultSet;
014 import java.sql.ResultSetMetaData;
015 import java.sql.SQLException;
016 import java.sql.Types;
017 import java.util.Locale;
018 import java.util.TimeZone;
019 import java.util.logging.Level;
020 import java.util.logging.Logger;
021
022 /**
023 Default implementation of {@link ConvertColumn}, suitable for most applications.
024
025 <P>This class converts non-<tt>null</tt> items using :
026 <table border='1' cellspacing='0' cellpadding='3'>
027 <tr><th>Target Class</th><th>Use</th></tr>
028 <tr><td><tt> SafeText </tt></td><td><tt> ResultSet.getString(), or ResultSet.getClob() </tt></td></tr>
029 <tr><td><tt> String (if allowed) </tt></td><td><tt> ResultSet.getString(), or ResultSet.getClob() </tt></td></tr>
030 <tr><td><tt> Integer </tt></td><td><tt>ResultSet.getInt()</tt></td></tr>
031 <tr><td><tt> Long </tt></td><td><tt> ResultSet.getLong() </tt></td></tr>
032 <tr><td><tt> Boolean </tt></td><td><tt> ResultSet.getBoolean() </tt></td></tr>
033 <tr><td><tt> BigDecimal </tt></td><td><tt> ResultSet.getBigDecimal() </tt></td></tr>
034 <tr><td><tt> Decimal</tt></td><td><tt> ResultSet.getBigDecimal()</tt></td></tr>
035 <tr><td><tt> Id </tt></td><td><tt> ResultSet.getString(), new Id(String)</tt></td></tr>
036 <tr><td><tt> DateTime </tt></td><td><tt> ResultSet.getString()</tt>, pass to {@link DateTime#DateTime(String)}</td></tr>
037 <tr><td><tt> Date </tt></td><td><tt> ResultSet.getTimestamp()</tt>, possibly with hint provided in <tt>web.xml</tt></td></tr>
038 <tr><td><tt> Locale </tt></td><td><tt> ResultSet.getString(), {@link hirondelle.web4j.util.Util#buildLocale(String)}</tt></td></tr>
039 <tr><td><tt> TimeZone </tt></td><td><tt> ResultSet.getString(), {@link hirondelle.web4j.util.Util#buildTimeZone(String)}</tt></td></tr>
040 </table>
041
042 <P>This implementation supports the same building block classes defined by another
043 default implementation : {@link hirondelle.web4j.model.ConvertParamImpl#isSupported(Class)}.
044 See that class for important information on the conditional support of <tt>String</tt>.
045 */
046 public class ConvertColumnImpl implements ConvertColumn {
047
048 /**
049 Defines policies for converting a column of a <tt>ResultSet</tt> into a possibly-null
050 <tt>Object</tt>.
051
052 @param aRow of a <tt>ResultSet</tt>
053 @param aColumnIdx particular column of aRow
054 @param aSupportedTargetType is a class supported by the configured implementation of
055 {@link ConvertParam#isSupported(Class)}.
056 */
057 public <T> T convert(ResultSet aRow, int aColumnIdx, Class<T> aSupportedTargetType) throws SQLException {
058 if( ! fConvertParam.isSupported(aSupportedTargetType) ){
059 throw new IllegalArgumentException("Unsupported Target Type : " + Util.quote(aSupportedTargetType));
060 }
061
062 Object result = null;
063 if (aSupportedTargetType == SafeText.class){
064 result = convertToSafeText(aRow, aColumnIdx);
065 }
066 else if (aSupportedTargetType == String.class) {
067 result = convertToString(aRow, aColumnIdx);
068 }
069 else if (aSupportedTargetType == Integer.class || aSupportedTargetType == int.class){
070 int value = aRow.getInt(aColumnIdx);
071 result = aRow.wasNull() ? null : new Integer(value);
072 }
073 else if (aSupportedTargetType == Boolean.class || aSupportedTargetType == boolean.class){
074 boolean value = aRow.getBoolean(aColumnIdx);
075 result = aRow.wasNull() ? null : Boolean.valueOf(value);
076 }
077 else if (aSupportedTargetType == BigDecimal.class){
078 result = aRow.getBigDecimal(aColumnIdx);
079 }
080 else if (aSupportedTargetType == java.util.Date.class){
081 result = getDate(aRow, aColumnIdx);
082 }
083 else if (aSupportedTargetType == DateTime.class){
084 result = getDateTime(aRow, aColumnIdx);
085 }
086 else if (aSupportedTargetType == Long.class || aSupportedTargetType == long.class){
087 long value = aRow.getLong(aColumnIdx);
088 result = aRow.wasNull() ? null : new Long(value);
089 }
090 else if (aSupportedTargetType == Id.class){
091 String value = aRow.getString(aColumnIdx);
092 result = aRow.wasNull() ? null : new Id(value);
093 }
094 else if (aSupportedTargetType == Locale.class){
095 String value = aRow.getString(aColumnIdx);
096 result = value == null ? null : Util.buildLocale(value);
097 }
098 else if (aSupportedTargetType == TimeZone.class){
099 String value = aRow.getString(aColumnIdx);
100 result = value == null ? null : Util.buildTimeZone(value);
101 }
102 else if (aSupportedTargetType == Decimal.class){
103 BigDecimal value = aRow.getBigDecimal(aColumnIdx);
104 result = value == null ? null : new Decimal(value);
105 }
106 else {
107 throw new AssertionError(
108 "Unsupported type cannot be translated to an object:" + aSupportedTargetType
109 );
110 }
111 fLogger.finest(
112 "Successfully converted ResultSet column idx " + Util.quote(aColumnIdx) +
113 " into a " + aSupportedTargetType.getName()
114 );
115 return (T)result; //this cast is unavoidable, and safe.
116 }
117
118 // PRIVATE
119 private final ConvertParam fConvertParam = BuildImpl.forConvertParam();
120 private static final Logger fLogger = Util.getLogger(ConvertColumnImpl.class);
121
122 private SafeText convertToSafeText(ResultSet aRow, int aColumnIdx) throws SQLException {
123 String result = convertToString(aRow, aColumnIdx);
124 return result == null ? null : new SafeText(result);
125 }
126
127 private String convertToString(ResultSet aRow, int aColumnIdx) throws SQLException {
128 String result = null;
129 if ( isClob(aRow, aColumnIdx) ) {
130 result = convertClobToString(aRow, aColumnIdx);
131 }
132 else {
133 result = aRow.getString(aColumnIdx);
134 }
135 return aRow.wasNull() ? null : result;
136 }
137
138 private static boolean isClob(ResultSet aRow, int aColumnIdx) throws SQLException {
139 ResultSetMetaData metaData = aRow.getMetaData();
140 boolean result = metaData.getColumnType(aColumnIdx) == Types.CLOB;
141 return result;
142 }
143
144 private static String convertClobToString(ResultSet aRow, int aColumnIdx) throws SQLException {
145 String result = null;
146 Clob clob = aRow.getClob(aColumnIdx);
147 if (clob != null){
148 int length = new Long(clob.length()).intValue();
149 result = clob.getSubString(1, length);
150 }
151 return result;
152 }
153
154 private java.util.Date getDate(ResultSet aRow, int aColumnIdx) throws SQLException {
155 java.util.Date result = null;
156 if ( DbConfig.hasTimeZoneHint() ){
157 result = aRow.getTimestamp(aColumnIdx, DbConfig.getTimeZoneHint());
158 }
159 else {
160 result = aRow.getTimestamp(aColumnIdx);
161 }
162 return result;
163 }
164
165 private DateTime getDateTime(ResultSet aRow, int aColumnIdx) throws SQLException {
166 //the ctor takes any String; if malformed, the DateTime will blow up later, when you call most other methods
167 String rawDateTime = aRow.getString(aColumnIdx);
168 if(fLogger.getLevel() == Level.FINEST){
169 fLogger.finest("DateTime column, raw value from database is: " + Util.quote(rawDateTime) +
170 ". SQL date-time formatting functions can be used to render the format compatible with the hirondelle.web4.model.DateTime class, if needed."
171 );
172 }
173 return rawDateTime == null ? null : new DateTime(rawDateTime);
174 }
175 }