classes/hirondelle/web4j/database/ConvertColumnImpl.java
changeset 0 3060119b1292
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/classes/hirondelle/web4j/database/ConvertColumnImpl.java	Wed Dec 04 17:00:31 2013 +0100
@@ -0,0 +1,183 @@
+package hirondelle.web4j.database;
+
+import hirondelle.web4j.BuildImpl;
+import hirondelle.web4j.model.ConvertParam;
+import hirondelle.web4j.model.DateTime;
+import hirondelle.web4j.model.Decimal;
+import hirondelle.web4j.model.Id;
+import hirondelle.web4j.readconfig.Config;
+import hirondelle.web4j.security.SafeText;
+import hirondelle.web4j.util.Util;
+
+import java.io.InputStream;
+import java.math.BigDecimal;
+import java.sql.Clob;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.sql.Types;
+import java.util.Locale;
+import java.util.TimeZone;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ Default implementation of {@link ConvertColumn}, suitable for most applications.
+ 
+ <P>This class converts non-<tt>null</tt> items using :
+ <table border='1' cellspacing='0' cellpadding='3'>
+  <tr><th>Target Class</th><th>Use</th></tr>
+  <tr><td><tt> SafeText </tt></td><td><tt> ResultSet.getString(), or ResultSet.getClob() </tt></td></tr>
+  <tr><td><tt> String (if allowed) </tt></td><td><tt> ResultSet.getString(), or ResultSet.getClob() </tt></td></tr>
+  <tr><td><tt> Integer </tt></td><td><tt>ResultSet.getInt()</tt></td></tr>
+  <tr><td><tt> Long </tt></td><td><tt> ResultSet.getLong() </tt></td></tr>
+  <tr><td><tt> Boolean </tt></td><td><tt> ResultSet.getBoolean() </tt></td></tr>
+  <tr><td><tt> BigDecimal </tt></td><td><tt> ResultSet.getBigDecimal() </tt></td></tr>
+  <tr><td><tt> Decimal</tt></td><td><tt> ResultSet.getBigDecimal()</tt></td></tr>
+  <tr><td><tt> Id </tt></td><td><tt> ResultSet.getString(), new Id(String)</tt></td></tr>
+  <tr><td><tt> DateTime </tt></td><td><tt> ResultSet.getString()</tt>, pass to {@link DateTime#DateTime(String)}</td></tr>
+  <tr><td><tt> Date </tt></td><td><tt> ResultSet.getTimestamp()</tt>, possibly with hint provided in <tt>web.xml</tt></td></tr>
+  <tr><td><tt> Locale </tt></td><td><tt> ResultSet.getString(), {@link hirondelle.web4j.util.Util#buildLocale(String)}</tt></td></tr>
+  <tr><td><tt> TimeZone </tt></td><td><tt> ResultSet.getString(), {@link hirondelle.web4j.util.Util#buildTimeZone(String)}</tt></td></tr>
+  <tr><td><tt> InputStream</tt></td><td><tt> ResultSet.getBinaryStream()</tt></td></tr>
+ </table>
+ 
+ <P>This implementation supports the same building block classes defined by another  
+ default implementation : {@link hirondelle.web4j.model.ConvertParamImpl#isSupported(Class)}.
+ See that class for important information on the conditional support of <tt>String</tt>.
+*/
+public class ConvertColumnImpl implements ConvertColumn {
+
+  /**
+   Defines policies for converting a column of a <tt>ResultSet</tt> into a possibly-null
+   <tt>Object</tt>.
+  
+   @param aRow of a <tt>ResultSet</tt>
+   @param aColumnIdx particular column of aRow
+   @param aSupportedTargetType is a class supported by the configured implementation of 
+   {@link ConvertParam#isSupported(Class)}.
+  */
+  public <T> T convert(ResultSet aRow, int aColumnIdx, Class<T> aSupportedTargetType) throws SQLException {
+    if( ! fConvertParam.isSupported(aSupportedTargetType) ){
+      throw new IllegalArgumentException("Unsupported Target Type : " + Util.quote(aSupportedTargetType)); 
+    }
+    
+    Object result = null;
+    if (aSupportedTargetType == SafeText.class){
+      result = convertToSafeText(aRow, aColumnIdx);
+    }
+    else if (aSupportedTargetType == String.class) {
+      result = convertToString(aRow, aColumnIdx);
+    }
+    else if (aSupportedTargetType == Integer.class ||  aSupportedTargetType == int.class){
+      int value = aRow.getInt(aColumnIdx);
+      result = aRow.wasNull() ? null : new Integer(value);
+    }
+    else if (aSupportedTargetType == Boolean.class || aSupportedTargetType == boolean.class){
+      boolean value = aRow.getBoolean(aColumnIdx);
+      result = aRow.wasNull() ? null : Boolean.valueOf(value);
+    }
+    else if (aSupportedTargetType == BigDecimal.class){
+      result =  aRow.getBigDecimal(aColumnIdx);
+    }
+    else if (aSupportedTargetType == java.util.Date.class){
+      result = getDate(aRow, aColumnIdx);
+    }
+    else if (aSupportedTargetType == DateTime.class){
+      result = getDateTime(aRow, aColumnIdx);
+    }
+    else if (aSupportedTargetType == Long.class || aSupportedTargetType == long.class){
+      long value = aRow.getLong(aColumnIdx);
+      result = aRow.wasNull() ? null : new Long(value);
+    }
+    else if (aSupportedTargetType == Id.class){
+      String value = aRow.getString(aColumnIdx);
+      result = aRow.wasNull() ? null : new Id(value);
+    }
+    else if (aSupportedTargetType == Locale.class){
+      String value = aRow.getString(aColumnIdx);
+      result = value == null ? null : Util.buildLocale(value);
+    }
+    else if (TimeZone.class == aSupportedTargetType){
+      String value = aRow.getString(aColumnIdx);
+      result = value == null ? null : Util.buildTimeZone(value);
+    }
+    else if (aSupportedTargetType == Decimal.class){
+      BigDecimal value =  aRow.getBigDecimal(aColumnIdx);
+      result = value == null ? null : new Decimal(value);
+    }
+    else if (InputStream.class.isAssignableFrom(aSupportedTargetType)){
+      InputStream value =  aRow.getBinaryStream(aColumnIdx);
+      result = value == null ? null : value; 
+    }
+    else {
+      throw new AssertionError(
+        "Unsupported type cannot be translated to an object:" + aSupportedTargetType
+      );
+    }
+    fLogger.finest(
+      "Successfully converted ResultSet column idx " + Util.quote(aColumnIdx) + 
+      " into a " + aSupportedTargetType.getName() 
+    );
+    return (T)result; //this cast is unavoidable, and safe.
+  }
+
+  // PRIVATE 
+  private final ConvertParam fConvertParam = BuildImpl.forConvertParam();
+  private static final Logger fLogger = Util.getLogger(ConvertColumnImpl.class);
+  
+  private SafeText convertToSafeText(ResultSet aRow, int aColumnIdx) throws SQLException {
+    String result = convertToString(aRow, aColumnIdx);
+    return result == null ? null : new SafeText(result);
+  }
+  
+  private String convertToString(ResultSet aRow, int aColumnIdx) throws SQLException {
+    String result = null;
+    if ( isClob(aRow, aColumnIdx) ) {
+      result = convertClobToString(aRow, aColumnIdx);
+    }
+    else {
+      result = aRow.getString(aColumnIdx);
+    }
+    return aRow.wasNull() ? null : result;
+  }
+  
+  private static boolean isClob(ResultSet aRow, int aColumnIdx) throws SQLException {
+    ResultSetMetaData metaData = aRow.getMetaData();
+    boolean result = metaData.getColumnType(aColumnIdx) == Types.CLOB;
+    return result;
+  }
+
+  private static String convertClobToString(ResultSet aRow, int aColumnIdx) throws SQLException {
+    String result = null;
+    Clob clob = aRow.getClob(aColumnIdx);
+    if (clob != null){
+      int length = new Long(clob.length()).intValue();
+      result = clob.getSubString(1, length);
+    }
+    return result;
+  }
+  
+  private java.util.Date getDate(ResultSet aRow, int aColumnIdx) throws SQLException {
+    java.util.Date result = null;
+    Config config = new Config();
+    if ( config.hasTimeZoneHint() ){
+      result = aRow.getTimestamp(aColumnIdx, config.getTimeZoneHint());
+    }
+    else {
+      result = aRow.getTimestamp(aColumnIdx);
+    }
+    return result;
+  }
+  
+  private DateTime getDateTime(ResultSet aRow, int aColumnIdx) throws SQLException {
+    //the ctor takes any String; if malformed, the DateTime will blow up later, when you call most other methods
+    String rawDateTime = aRow.getString(aColumnIdx);
+    if(fLogger.getLevel() == Level.FINEST){
+      fLogger.finest("DateTime column, raw value from database is: " + Util.quote(rawDateTime) + 
+       ". SQL date-time formatting functions can be used to render the format compatible with the hirondelle.web4.model.DateTime class, if needed."
+      );
+    }
+    return rawDateTime == null ? null :  new DateTime(rawDateTime);
+  }
+}