/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package com.venky.swf.db; import java.io.IOException; import java.io.InputStream; import java.io.Reader; import java.math.BigDecimal; import java.sql.Blob; import java.sql.Clob; import java.sql.Date; import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Time; import java.sql.Timestamp; import java.sql.Types; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.TimeZone; import com.venky.cache.Cache; import com.venky.core.date.DateUtils; import com.venky.core.io.ByteArrayInputStream; import com.venky.core.io.StringReader; import com.venky.core.log.SWFLogger; import com.venky.core.log.TimerStatistics.Timer; import com.venky.core.math.DoubleUtils; import com.venky.core.string.StringUtil; import com.venky.core.util.Bucket; import com.venky.core.util.ExceptionUtil; import com.venky.core.util.ObjectUtil; import com.venky.swf.db.annotations.column.COLUMN_DEF; import com.venky.swf.db.annotations.column.defaulting.StandardDefault; import com.venky.swf.db.annotations.column.defaulting.StandardDefaulter; import com.venky.swf.db.model.Model; import com.venky.swf.db.table.Table; import com.venky.swf.routing.Config; /** * * @author venky */ public abstract class JdbcTypeHelper { public boolean supportsLimitSyntax(){ return true; } public boolean isSavepointManagedByJdbc(){ return true; } public boolean isAutoCommitOnDDL(){ return false; } public String getEstablishSavepointStatement(String name){ return null; } public String getReleaseSavepointStatement(String name){ return null; } public String getRollbackToSavepointStatement(String name){ return null; } public boolean requiresSeparatePrimaryKeyClause(){ return true; } public boolean isColumnNameAutoLowerCasedInDB(){ return false; } private static Class<?> getClass(String name){ try { return Class.forName(name); }catch (Exception ex){ return null; } } private static Class<?> sqlTransactionRollbackException(){ return getClass("java.sql.SQLTransactionRollbackException"); } private Class<?> queryTimeoutException(){ return getClass("java.sql.SQLTimeoutException"); } public boolean hasTransactionRolledBack(Throwable ex){ Class<?> sqlTransactionRollBackException = sqlTransactionRollbackException(); if (sqlTransactionRollBackException == null){ return false; } return getEmbeddedException(ex,sqlTransactionRollBackException) != null ; } public boolean isQueryTimeoutSupported(){ return true; } public boolean isNoWaitSupported(){ return false; } public String getNoWaitLiteral(){ return ""; } public String getForUpdateLiteral(){ return " FOR UPDATE "; } public boolean isQueryTimeoutException(SQLException ex){ Class<?> queryTimeOutException = queryTimeoutException(); if (queryTimeOutException != null && queryTimeOutException.isInstance(ex)){ return true; } return false; } public static class TypeRef<M> { int jdbcType; String sqlType; int size; int scale; TypeConverter<M> typeConverter; Class<?> javaClass; boolean quotedWhenUnbounded; boolean columnDefaultQuoted; public TypeRef(int jdbcType, String sqlType, int size, int scale, boolean quotedWhenUnbounded, boolean columnDefaultQuoted, TypeConverter<M> typeConverter) { this.jdbcType = jdbcType; this.sqlType = sqlType; this.size = size; this.scale = scale; this.quotedWhenUnbounded = quotedWhenUnbounded; this.columnDefaultQuoted = columnDefaultQuoted; this.typeConverter = typeConverter; } public boolean isColumnDefaultQuoted() { return columnDefaultQuoted; } public boolean isQuotedWhenUnbounded() { return quotedWhenUnbounded; } public int getJdbcType() { return jdbcType; } public int getSize() { return size; } public String getSqlType() { return sqlType; } public int getScale() { return scale; } public boolean isLOB(){ return JdbcTypeHelper.isLOB(jdbcType); } public boolean isCLOB(){ return JdbcTypeHelper.isCLOB(jdbcType); } public boolean isBLOB(){ return JdbcTypeHelper.isBLOB(jdbcType); } public boolean isNumeric(){ return JdbcTypeHelper.isNumeric(javaClass); } public Class<?> getJavaClass(){ return javaClass; } public void setJavaClass(Class<?> javaClass){ this.javaClass = javaClass; } public TypeConverter<M> getTypeConverter() { return typeConverter; } } private static int[] CLOBTYPES = new int[] {Types.CLOB,Types.LONGVARCHAR} ; static { Arrays.sort(CLOBTYPES); } private static int[] BLOBTYPES = new int[] {Types.BLOB , Types.LONGVARBINARY , Types.BINARY, Types.VARBINARY} ; static { Arrays.sort(BLOBTYPES); } private static Class<?>[] NUMERICTYPES = new Class[] { int.class, short.class, long.class, float.class, double.class, Number.class }; public static boolean isNumeric(Class<?> clazz){ if (clazz == null){ throw new NullPointerException("Trying to find if null class is numeric!"); } for (int i = 0 ; i < NUMERICTYPES.length ; i ++ ){ if (NUMERICTYPES[i].isAssignableFrom(clazz)){ return true; } } return false; } public static boolean isCLOB(int jdbcType) { return Arrays.binarySearch(CLOBTYPES,jdbcType)> 0; } public static boolean isBLOB(int jdbcType) { return Arrays.binarySearch(BLOBTYPES,jdbcType)> 0; } public static boolean isLOB(int jdbcType){ return isCLOB(jdbcType) || isBLOB(jdbcType); } public abstract class TypeConverter<M> { public abstract M valueOf(Object o); public String toString(Object m) { return StringUtil.valueOf(m); } public String toStringISO(Object m) { return toString(m) ; } public abstract String getDisplayClassName(); } public class BooleanConverter extends TypeConverter<Boolean> { public Boolean valueOf(Object s) { if (ObjectUtil.isVoid(s)) { return false; } return Boolean.valueOf(StringUtil.valueOf(s).equalsIgnoreCase("true") || StringUtil.valueOf(s).equalsIgnoreCase("1") || StringUtil.valueOf(s).equalsIgnoreCase("Y") || StringUtil.valueOf(s).equalsIgnoreCase("YES")); } public String toString(Object m) { if (m != null){ Boolean v = valueOf(m); if (v){ return "Y"; }else { return "N"; } } return StringUtil.valueOf(m); } @Override public String getDisplayClassName() { return "boolean"; } } public class CharacterConverter extends TypeConverter<Character> { public Character valueOf(Object object) { if (ObjectUtil.isVoid(object)) { return new Character((char) 0); } char[] c = StringUtil.valueOf(object).toCharArray(); if (c.length == 1) { return Character.valueOf(c[0]); } throw new RuntimeException("Cannot convert String " + String.valueOf(object) + " to character"); } @Override public String getDisplayClassName() { return "char"; } } public class ByteConverter extends TypeConverter<Byte> { public Byte valueOf(Object o) { if (ObjectUtil.isVoid(o)) { return new Byte((byte) 0); } return Byte.valueOf(StringUtil.valueOf(o)); } @Override public String getDisplayClassName() { return "byte"; } } public abstract class NumberConverter<N extends Number> extends TypeConverter<N> { @Override public String getDisplayClassName() { return "number text-right"; } } @SuppressWarnings("unchecked") public abstract class NumericConverter<N extends Number> extends NumberConverter<N> { public DecimalFormat getDisplayFormat(){ return new DecimalFormat("##############.0000"); } public String toString(Object o){ if (o == null){ return ""; } N n = (N)o; double fract = n.doubleValue() - Math.floor(n.doubleValue()); DecimalFormat fmt = getDisplayFormat(); if (DoubleUtils.compareTo(fract ,Math.round(fract*100.0)/100.0)== 0){ fmt = new DecimalFormat("##############.00"); } return fmt.format(n.doubleValue()); } } public class ShortConverter extends NumberConverter<Short> { public Short valueOf(Object o) { DoubleConverter dc = (DoubleConverter) getTypeRef(Double.class).getTypeConverter(); return dc.valueOf(o).shortValue(); } } public class IntegerConverter extends NumberConverter<Integer> { public Integer valueOf(Object o) { DoubleConverter dc = (DoubleConverter) getTypeRef(Double.class).getTypeConverter(); return dc.valueOf(o).intValue(); } } public class LongConverter extends NumberConverter<Long> { public Long valueOf(Object o) { DoubleConverter dc = (DoubleConverter) getTypeRef(Double.class).getTypeConverter(); return dc.valueOf(o).longValue(); } } public class FloatConverter extends NumericConverter<Float> { public Float valueOf(Object o) { DoubleConverter dc = (DoubleConverter) getTypeRef(Double.class).getTypeConverter(); return dc.valueOf(o).floatValue(); } } public class DoubleConverter extends NumericConverter<Double> { public Double valueOf(Object o) { if (ObjectUtil.isVoid(o)) { return new Double(0.0); }else if (o instanceof Date){ return new Double(((Date)o).getTime()); }else if (o instanceof Boolean){ BooleanConverter bc = (BooleanConverter) getTypeRef(Boolean.class).getTypeConverter(); return new Double( bc.valueOf("1").equals(o)? 1 : 0); }else { return Double.valueOf(StringUtil.valueOf(o)); } } } public class BucketConverter extends NumericConverter<Bucket> { public Bucket valueOf(Object o) { if (ObjectUtil.isVoid(o)) { return new Bucket(0.0); }else if (o instanceof Date){ return new Bucket(((Date)o).getTime()); }else if (o instanceof Boolean){ BooleanConverter bc = (BooleanConverter) getTypeRef(Boolean.class).getTypeConverter(); return new Bucket( bc.valueOf("1").equals(o)? 1 : 0); }else { return new Bucket(Double.valueOf(StringUtil.valueOf(o))); } } } public class BigDecimalConverter extends NumericConverter<BigDecimal> { public DecimalFormat getDisplayFormat(){ return new DecimalFormat("###############.0000000000"); } public BigDecimal valueOf(Object o) { if (ObjectUtil.isVoid(o)) { return new BigDecimal(0.0); } return new BigDecimal(StringUtil.valueOf(o)); } } public class StringConverter extends TypeConverter<String> { public String valueOf(Object o) { return StringUtil.valueOf(o); } public String toString(Object o) { return StringUtil.valueOf(o); } @Override public String getDisplayClassName() { return "string"; } } public class DateConverter extends TypeConverter<Date> { private String format = null; private TimeZone tz = null; public DateConverter(){ this(DateUtils.APP_DATE_FORMAT_STR,TimeZone.getDefault()); } public DateConverter(String format,TimeZone tz){ this.format = format; this.tz = tz; } public Date valueOf(Object o) { if (ObjectUtil.isVoid(o)) { return null; } if (o instanceof java.util.Date) { return new Date(((java.util.Date) o).getTime()); } return new Date(DateUtils.getDate(StringUtil.valueOf(o)).getTime()); } public String toString(Object date) { return date == null ? "" : (date instanceof String ? (String)date : DateUtils.getTimestampStr(valueOf(date),tz,format)); } public String toStringISO(Object date) { return date == null ? "" : (date instanceof String ? (String)date : DateUtils.getTimestampStr(valueOf(date),tz,DateUtils.ISO_DATE_FORMAT_STR)); } @Override public String getDisplayClassName() { return "date"; } } public class TimeConverter extends TypeConverter<Time> { String format = null ; TimeZone tz = null; public TimeConverter(){ this(DateUtils.APP_TIME_FORMAT_STR,TimeZone.getDefault()); } public TimeConverter(String format,TimeZone tz){ this.format = format; } public Time valueOf(Object o) { if (ObjectUtil.isVoid(o)) { return null; } if (o instanceof Time){ return new Time(((Time)o).getTime()); } return new Time(DateUtils.getTime(StringUtil.valueOf(o)).getTime()); } public String toString(Object time) { return time == null ? "" : (time instanceof String ? (String)time : DateUtils.getTimestampStr(valueOf(time),tz, format)); } public String toStringISO(Object time) { return time == null ? "" : (time instanceof String ? (String)time : DateUtils.getTimestampStr(valueOf(time),tz,DateUtils.ISO_DATE_TIME_FORMAT_STR)); } @Override public String getDisplayClassName() { return "time"; } } public class TimestampConverter extends TypeConverter<Timestamp> { public TimestampConverter(){ this(DateUtils.APP_DATE_TIME_FORMAT_STR,TimeZone.getDefault()); } private String format; private TimeZone tz; public TimestampConverter(String format,TimeZone tz){ this.format = format; this.tz = tz; } public Timestamp valueOf(Object o) { if (ObjectUtil.isVoid(o)) { return null; } if (o instanceof Timestamp){ return new Timestamp(((Timestamp)o).getTime()); } return new Timestamp(DateUtils.getDate(StringUtil.valueOf(o)).getTime()); } public String toString(Object ts) { return ts == null ? "" : (ts instanceof String ? (String)ts : DateUtils.getTimestampStr(valueOf(ts),tz,format)); } public String toStringISO(Object ts) { return ts == null ? "" : (ts instanceof String ? (String)ts : DateUtils.getTimestampStr(valueOf(ts),tz,DateUtils.ISO_DATE_TIME_FORMAT_STR)); } @Override public String getDisplayClassName() { return "timestamp"; } } public class InputStreamConverter extends TypeConverter<InputStream> { public InputStream valueOf(Object o) { if (o == null) { return null; } if (o instanceof Blob) { Blob b = (Blob) o; try { return new ByteArrayInputStream(StringUtil.readBytes(b.getBinaryStream())); } catch (SQLException e) { throw new RuntimeException(e); } } if (o instanceof InputStream) { if (o instanceof ByteArrayInputStream){ ByteArrayInputStream is = (ByteArrayInputStream)o; is.reset(); return is; }else { return new ByteArrayInputStream(StringUtil.readBytes((InputStream)o)); } } if (o instanceof byte[]){ return new ByteArrayInputStream((byte[])o); } return new ByteArrayInputStream(StringUtil.valueOf(o).getBytes()); } public String toString(Object in) { return in == null ? "" : (in instanceof String ? (String)in : StringUtil.read(valueOf(in))); } @Override public String getDisplayClassName() { return "string"; } } public class ReaderConverter extends TypeConverter<Reader> { public Reader valueOf(Object o) { if (o == null) { return null; } if (o instanceof Clob) { Clob b = (Clob) o; try { return new StringReader(StringUtil.read(b.getCharacterStream())); } catch (SQLException e) { throw new RuntimeException(e); } } if (o instanceof Reader) { if (o instanceof StringReader){ try { ((StringReader)o).reset(); } catch (IOException e) { throw new RuntimeException(e); } return (Reader) o; }else { return new StringReader(StringUtil.read((Reader)o)); } } return new StringReader(StringUtil.valueOf(o)); } public String toString(Object reader) { return reader == null ? "" : (reader instanceof String ? (String)reader : StringUtil.read(valueOf(reader))); } @Override public String getDisplayClassName() { return "string"; } } private static Cache<Class<?>,JdbcTypeHelper> _instance = new Cache<Class<?>, JdbcTypeHelper>() { /** * */ private static final long serialVersionUID = -3456286258113436179L; @Override protected JdbcTypeHelper getValue(Class<?> driverClass) { if (driverClass.getName().startsWith("org.apache.derby.")){ return new DerbyHelper(); }else if (driverClass.getName().startsWith("com.mysql")) { return new MySqlHelper(); }else if (driverClass.getName().startsWith("org.postgresql")) { return new PostgresqlHelper(); }else if (driverClass.getName().startsWith("org.sqlite")){ return new SQLiteHelper(); }else if (driverClass.getName().startsWith("org.sqldroid")) { return new SQLDroidHelper(); }else if (driverClass.getName().startsWith("org.h2")) { return new H2Helper(); } return null; } }; public static JdbcTypeHelper instance(Class<?> driverClass){ return _instance.get(driverClass); } private final Map<Class<?>, TypeRef<?>> javaTypeRefMap = new HashMap<Class<?>, TypeRef<?>>(); private final Map<Integer, List<TypeRef<?>>> jdbcTypeRefMap = new HashMap<Integer, List<TypeRef<?>>>(); protected <T> void registerjdbcSQLType(Class<T> clazz, TypeRef<T> ref) { ref.setJavaClass(clazz); javaTypeRefMap.put(clazz, ref); List<TypeRef<?>> colTypeRefs = jdbcTypeRefMap.get(ref.jdbcType); if (colTypeRefs == null){ colTypeRefs = new ArrayList<TypeRef<?>>(); jdbcTypeRefMap.put(ref.jdbcType, colTypeRefs); } colTypeRefs.add(ref); } public TypeRef<?> getTypeRef(Class<?> javaClass) { TypeRef<?> ref = javaTypeRefMap.get(javaClass); if (ref != null) { return ref; } Timer loop = cat.startTimer("loop:" + javaClass.getName(), Config.instance().isTimerAdditive()); try { for (Class<?> key : javaTypeRefMap.keySet()) { if (key.isAssignableFrom(javaClass)) { TypeRef<?> value = javaTypeRefMap.get(key); javaTypeRefMap.put(key, value); return value; } } return null; }finally{ loop.stop(); } } public List<TypeRef<?>> getTypeRefs(int jdbcType) { List<TypeRef<?>> refs = jdbcTypeRefMap.get(jdbcType); if (refs == null){ Config.instance().getLogger(getClass().getName()).warning("Cannot find ref for jdbcType:" + jdbcType); } return refs; } public TypeRef<?> getTypeRef(int jdbcType) { List<TypeRef<?>> refs = getTypeRefs(jdbcType); if (refs == null || refs.isEmpty()){ Config.instance().getLogger(getClass().getName()).warning("Cannot find ref for jdbcType:" + jdbcType); return null; }else{ return refs.get(0); } } public String getAutoIncrementInstruction() { return " INTEGER NOT NULL "; } public abstract String getCurrentTimeStampKW(); public abstract String getCurrentDateKW(); public String toDefaultKW(TypeRef<?> ref, COLUMN_DEF def){ Timer timer = cat.startTimer(null,Config.instance().isTimerAdditive()); try { if (def.value() == StandardDefault.CURRENT_TIMESTAMP){ return getCurrentTimeStampKW(); }else if (def.value() == StandardDefault.CURRENT_DATE){ return getCurrentDateKW(); }else { return getDefaultKW(ref,StandardDefaulter.getDefaultValue(def)); } }finally{ timer.stop(); } } public String getDefaultKW(TypeRef<?> ref, Object value){ if ( ref.isColumnDefaultQuoted() && !StringUtil.valueOf(value).startsWith("'")){ return "'" + value + "'"; }else { return StringUtil.valueOf(value); } } public static Throwable getEmbeddedException(Throwable in, Class<?> instanceOfThisClass){ return ExceptionUtil.getEmbeddedException(in, instanceOfThisClass); } public boolean isVoid (Object o){ return o == null || ObjectUtil.equals(getTypeRef(o.getClass()).getTypeConverter().valueOf(null),o); } protected <M extends Model> void updateSequence(Table<M> table){ Config.instance().getLogger(getClass().getName()).warning("updateSequence not implemented in " + getClass().getName() ); } public String getLowerCaseFunction() { return "LOWER"; } public void setBinaryStream(PreparedStatement st, int i, ByteArrayInputStream in) throws SQLException { st.setBinaryStream(i, in, in.available()); } private final SWFLogger cat = new SWFLogger(Config.instance().getLogger(getClass().getName())); }