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