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 }