View Javadoc
1   package com.github.davidmoten.rx.jdbc;
2   
3   import java.io.ByteArrayInputStream;
4   import java.io.IOException;
5   import java.io.InputStream;
6   import java.io.OutputStream;
7   import java.io.Reader;
8   import java.io.StringReader;
9   import java.io.Writer;
10  import java.lang.reflect.Constructor;
11  import java.lang.reflect.InvocationTargetException;
12  import java.lang.reflect.Method;
13  import java.math.BigDecimal;
14  import java.math.BigInteger;
15  import java.sql.Blob;
16  import java.sql.Clob;
17  import java.sql.Connection;
18  import java.sql.PreparedStatement;
19  import java.sql.ResultSet;
20  import java.sql.ResultSetMetaData;
21  import java.sql.SQLException;
22  import java.sql.Time;
23  import java.sql.Timestamp;
24  import java.sql.Types;
25  import java.util.ArrayList;
26  import java.util.Calendar;
27  import java.util.Collections;
28  import java.util.HashMap;
29  import java.util.List;
30  import java.util.Map;
31  
32  import org.apache.commons.io.IOUtils;
33  import org.slf4j.Logger;
34  import org.slf4j.LoggerFactory;
35  
36  import rx.functions.Func1;
37  
38  import com.github.davidmoten.rx.jdbc.QuerySelect.Builder;
39  import com.github.davidmoten.rx.jdbc.exceptions.SQLRuntimeException;
40  
41  /**
42   * Utility methods.
43   */
44  public final class Util {
45  
46      /**
47       * Private constructor to prevent instantiation.
48       */
49      private Util() {
50          // prevent instantiation
51      }
52  
53      /**
54       * Logger.
55       */
56      private static final Logger log = LoggerFactory.getLogger(Util.class);
57  
58      /**
59       * Count the number of JDBC parameters in a sql statement.
60       * 
61       * @param query
62       *            .sql()
63       * @return
64       */
65      static int parametersCount(Query query) {
66          if (query.names().isEmpty())
67              return countQuestionMarkParameters(query.sql());
68          else
69              return query.names().size();
70      }
71  
72      // Visible for testing
73      static int countQuestionMarkParameters(String sql) {
74          // was originally using regular expressions, but they didn't work well
75          // for ignoring parameter-like strings inside quotes.
76          int count = 0;
77          int length = sql.length();
78          boolean inSingleQuote = false;
79          boolean inDoubleQuote = false;
80          for (int i = 0; i < length; i++) {
81              char c = sql.charAt(i);
82              if (inSingleQuote) {
83                  if (c == '\'') {
84                      inSingleQuote = false;
85                  }
86              } else if (inDoubleQuote) {
87                  if (c == '"') {
88                      inDoubleQuote = false;
89                  }
90              } else {
91                  if (c == '\'') {
92                      inSingleQuote = true;
93                  } else if (c == '"') {
94                      inDoubleQuote = true;
95                  } else if (c == '?') {
96                      count++;
97                  }
98              }
99          }
100         return count;
101     }
102 
103     /**
104      * Cancels then closes a {@link PreparedStatement} and logs exceptions
105      * without throwing. Does nothing if ps is null.
106      * 
107      * @param ps
108      */
109     static void closeQuietly(PreparedStatement ps) {
110         try {
111             boolean isClosed;
112             try {
113                 if (ps != null)
114                     isClosed = ps.isClosed();
115                 else
116                     isClosed = true;
117             } catch (SQLException e) {
118                 log.debug(e.getMessage());
119                 isClosed = true;
120             }
121             if (ps != null && !isClosed) {
122                 try {
123                     ps.cancel();
124                     log.debug("cancelled {}", ps);
125                 } catch (SQLException e) {
126                     log.debug(e.getMessage());
127                 }
128                 ps.close();
129                 log.debug("closed {}", ps);
130             }
131         } catch (SQLException e) {
132             log.debug(e.getMessage(), e);
133         } catch (RuntimeException e) {
134             log.debug(e.getMessage(), e);
135         }
136     }
137 
138     /**
139      * Closes a {@link Connection} and logs exceptions without throwing. Does
140      * nothing if connection is null.
141      * 
142      * @param connection
143      */
144     static void closeQuietly(Connection connection) {
145         try {
146             if (connection != null && !connection.isClosed()) {
147                 connection.close();
148                 log.debug("closed {}", connection);
149             }
150         } catch (SQLException e) {
151             log.debug(e.getMessage(), e);
152         } catch (RuntimeException e) {
153             log.debug(e.getMessage(), e);
154         }
155     }
156 
157     /**
158      * Closes a {@link Connection} only if the connection is in auto commit mode
159      * and logs exceptions without throwing. Does nothing if connection is null.
160      * 
161      * @param connection
162      */
163     static boolean closeQuietlyIfAutoCommit(Connection connection) {
164         try {
165             if (connection != null && !connection.isClosed() && connection.getAutoCommit()) {
166                 closeQuietly(connection);
167                 return true;
168             } else
169                 return false;
170         } catch (SQLException e) {
171             throw new SQLRuntimeException(e);
172         }
173     }
174 
175     /**
176      * Commits a {@link Connection} and logs exceptions without throwing.
177      * 
178      * @param connection
179      */
180     static void commit(Connection connection) {
181         if (connection != null)
182             try {
183                 connection.commit();
184                 log.debug("committed");
185             } catch (SQLException e) {
186                 throw new SQLRuntimeException(e);
187             }
188     }
189 
190     /**
191      * Rolls back a {@link Connection} and logs exceptions without throwing.
192      * 
193      * @param connection
194      */
195     static void rollback(Connection connection) {
196         if (connection != null)
197             try {
198                 connection.rollback();
199                 log.debug("rolled back");
200             } catch (SQLException e) {
201                 throw new SQLRuntimeException(e);
202             }
203     }
204 
205     /**
206      * Closes a {@link ResultSet} and logs exceptions without throwing.
207      * 
208      * @param rs
209      */
210     static void closeQuietly(ResultSet rs) {
211         try {
212             if (rs != null && !rs.isClosed()) {
213                 rs.close();
214                 log.debug("closed {}", rs);
215             }
216         } catch (SQLException e) {
217             log.debug(e.getMessage(), e);
218         } catch (RuntimeException e) {
219             log.debug(e.getMessage(), e);
220         }
221     }
222 
223     /**
224      * Returns true if and only if {@link Connection} is in auto commit mode.
225      * 
226      * @param con
227      * @return
228      */
229     static boolean isAutoCommit(Connection con) {
230         try {
231             return con.getAutoCommit();
232         } catch (SQLException e) {
233             throw new SQLRuntimeException(e);
234         }
235     }
236 
237     /**
238      * Returns the empty list whenever called.
239      */
240     static Func1<Integer, List<Parameter>> TO_EMPTY_PARAMETER_LIST = new Func1<Integer, List<Parameter>>() {
241         @Override
242         public List<Parameter> call(Integer n) {
243             return Collections.emptyList();
244         };
245     };
246 
247     /**
248      * Returns a function that converts the ResultSet column values into
249      * parameters to the constructor (with number of parameters equals the
250      * number of columns) of type <code>cls</code> then returns an instance of
251      * type <code>cls</code>. See {@link Builder#autoMap(Class)}.
252      * 
253      * @param cls
254      * @return
255      */
256     static <T> ResultSetMapper<T> autoMap(final Class<T> cls) {
257         return new ResultSetMapper<T>() {
258             @Override
259             public T call(ResultSet rs) {
260                 return autoMap(rs, cls);
261             }
262         };
263     }
264 
265     /**
266      * Converts the ResultSet column values into parameters to the constructor
267      * (with number of parameters equals the number of columns) of type
268      * <code>T</code> then returns an instance of type <code>T</code>. See See
269      * {@link Builder#autoMap(Class)}.
270      * 
271      * @param cls
272      *            the class of the resultant instance
273      * @return an automapped instance
274      */
275     @SuppressWarnings("unchecked")
276     static <T> T autoMap(ResultSet rs, Class<T> cls) {
277         try {
278             if (cls.isInterface()) {
279                 return autoMapInterface(rs, cls);
280             } else {
281                 int n = rs.getMetaData().getColumnCount();
282                 for (Constructor<?> c : cls.getDeclaredConstructors()) {
283                     if (n == c.getParameterTypes().length) {
284                         return autoMap(rs, (Constructor<T>) c);
285                     }
286                 }
287                 throw new RuntimeException("constructor with number of parameters=" + n
288                         + "  not found in " + cls);
289             }
290         } catch (SQLException e) {
291             throw new SQLRuntimeException(e);
292         }
293     }
294 
295     private static <T> T autoMapInterface(ResultSet rs, Class<T> cls) {
296         return ProxyService.newInstance(rs, cls);
297     }
298 
299     static interface Col {
300         Class<?> returnType();
301     }
302 
303     static class NamedCol implements Col {
304         final String name;
305         private final Class<?> returnType;
306 
307         public NamedCol(String name, Class<?> returnType) {
308             this.name = name;
309             this.returnType = returnType;
310         }
311 
312         @Override
313         public Class<?> returnType() {
314             return returnType;
315         }
316 
317         @Override
318         public String toString() {
319             return "NamedCol [name=" + name + ", returnType=" + returnType + "]";
320         }
321 
322     }
323 
324     static class IndexedCol implements Col {
325         final int index;
326         private final Class<?> returnType;
327 
328         public IndexedCol(int index, Class<?> returnType) {
329             this.index = index;
330             this.returnType = returnType;
331         }
332 
333         @Override
334         public Class<?> returnType() {
335             return returnType;
336         }
337 
338         @Override
339         public String toString() {
340             return "IndexedCol [index=" + index + ", returnType=" + returnType + "]";
341         }
342 
343     }
344 
345     private static class ProxyService<T> implements java.lang.reflect.InvocationHandler {
346 
347         private final Map<String, Object> values = new HashMap<String, Object>();
348 
349         ProxyService(ResultSet rs, Class<T> cls) {
350             // load information from cache about the result set
351             if (Database.rsCache.get() == null || Database.rsCache.get().rs != rs)
352                 Database.rsCache.set(new ResultSetCache(rs));
353             Map<String, Integer> colIndexes = Database.rsCache.get().colIndexes;
354 
355             // load information from cache about the class
356             if (Database.autoMapCache.get() == null || Database.autoMapCache.get().cls != cls)
357                 Database.autoMapCache.set(new AutoMapCache(cls));
358             Map<String, Col> methodCols = Database.autoMapCache.get().methodCols;
359 
360             // calculate values for all the interface methods and put them in a
361             // map
362             for (Method m : cls.getMethods()) {
363                 String methodName = m.getName();
364                 Col column = methodCols.get(methodName);
365                 Integer index;
366                 if (column instanceof NamedCol) {
367                     String name = ((NamedCol) column).name;
368                     index = colIndexes.get(name.toUpperCase());
369                     if (index == null) {
370                         throw new SQLRuntimeException("query column names do not include '" + name
371                                 + "'");
372                     }
373                 } else {
374                     IndexedCol col = ((IndexedCol) column);
375                     index = col.index;
376                 }
377                 Object value = autoMap(getObject(rs, column.returnType(), index),
378                         column.returnType());
379                 values.put(methodName, value);
380             }
381         }
382 
383         @SuppressWarnings("unchecked")
384         public static <T> T newInstance(ResultSet rs, Class<T> cls) {
385 
386             return (T) java.lang.reflect.Proxy.newProxyInstance(cls.getClassLoader(),
387                     new Class[] { cls }, new ProxyService<T>(rs, cls));
388         }
389 
390         @Override
391         public Object invoke(Object proxy, Method m, Object[] args) throws Throwable {
392             return values.get(m.getName());
393         }
394     }
395 
396     static String camelCaseToUnderscore(String camelCased) {
397         // guava has best solution for this with CaseFormat class
398         // but don't want to add dependency just for this method
399         final String regex = "([a-z])([A-Z]+)";
400         final String replacement = "$1_$2";
401         return camelCased.replaceAll(regex, replacement);
402     }
403 
404     static String first(String[] value) {
405         if (value == null || value.length == 0)
406             return null;
407         else
408             return value[0];
409     }
410 
411     /**
412      * Converts the ResultSet column values into parameters to the given
413      * constructor (with number of parameters equals the number of columns) of
414      * type <code>T</code> then returns an instance of type <code>T</code>. See
415      * See {@link Builder#autoMap(Class)}.
416      * 
417      * @param rs
418      *            the result set row
419      * @param c
420      *            constructor to use for instantiation
421      * @return automapped instance
422      */
423     private static <T> T autoMap(ResultSet rs, Constructor<T> c) {
424         Class<?>[] types = c.getParameterTypes();
425         List<Object> list = new ArrayList<Object>();
426         for (int i = 0; i < types.length; i++) {
427             list.add(autoMap(getObject(rs, types[i], i + 1), types[i]));
428         }
429         try {
430             return newInstance(c, list);
431         } catch (RuntimeException e) {
432             throw new RuntimeException("problem with parameters=" + getTypeInfo(list)
433                     + ", rs types=" + getRowInfo(rs)
434                     + ". Be sure not to use primitives in a constructor when calling autoMap().", e);
435         }
436     }
437 
438     static <T> void setSqlFromQueryAnnotation(Class<T> cls, QueryBuilder builder) {
439         if (builder.sql() == null) {
440             com.github.davidmoten.rx.jdbc.annotations.Query query = cls
441                     .getAnnotation(com.github.davidmoten.rx.jdbc.annotations.Query.class);
442             if (query != null && query.value() != null) {
443                 String sql = query.value();
444                 builder.setSql(sql);
445             } else
446                 throw new RuntimeException(
447                         "Class "
448                                 + cls
449                                 + " must be annotated with @Query(sql) or sql must be specified to the builder.select() call");
450         }
451     }
452 
453     /**
454      * Returns debugging info about the types of a list of objects.
455      * 
456      * @param list
457      * @return
458      */
459     private static String getTypeInfo(List<Object> list) {
460 
461         StringBuilder s = new StringBuilder();
462         for (Object o : list) {
463             if (s.length() > 0)
464                 s.append(", ");
465             if (o == null)
466                 s.append("null");
467             else {
468                 s.append(o.getClass().getName());
469                 s.append("=");
470                 s.append(o);
471             }
472         }
473         return s.toString();
474     }
475 
476     private static String getRowInfo(ResultSet rs) {
477         StringBuilder s = new StringBuilder();
478         try {
479             ResultSetMetaData md = rs.getMetaData();
480             for (int i = 1; i <= md.getColumnCount(); i++) {
481                 String name = md.getColumnName(i);
482                 String type = md.getColumnClassName(i);
483                 if (s.length() > 0)
484                     s.append(", ");
485                 s.append(name);
486                 s.append("=");
487                 s.append(type);
488             }
489         } catch (SQLException e1) {
490             throw new SQLRuntimeException(e1);
491         }
492         return s.toString();
493     }
494 
495     /**
496      * 
497      * @param c
498      *            constructor to use
499      * @param parameters
500      *            constructor parameters
501      * @return
502      */
503     @SuppressWarnings("unchecked")
504     private static <T> T newInstance(Constructor<?> c, List<Object> parameters) {
505         try {
506             return (T) c.newInstance(parameters.toArray());
507         } catch (InstantiationException e) {
508             throw new RuntimeException(e);
509         } catch (IllegalAccessException e) {
510             throw new RuntimeException(e);
511         } catch (IllegalArgumentException e) {
512             throw new RuntimeException(e);
513         } catch (InvocationTargetException e) {
514             throw new RuntimeException(e);
515         }
516     }
517 
518     /**
519      * Converts from java.sql Types to common java types like java.util.Date and
520      * numeric types. See {@link Builder#autoMap(Class)}.
521      * 
522      * @param o
523      * @param cls
524      * @return
525      */
526     public static Object autoMap(Object o, Class<?> cls) {
527         if (o == null)
528             return o;
529         else if (cls.isAssignableFrom(o.getClass())) {
530             return o;
531         } else {
532             if (o instanceof java.sql.Date) {
533                 java.sql.Date d = (java.sql.Date) o;
534                 if (cls.isAssignableFrom(Long.class))
535                     return d.getTime();
536                 else if (cls.isAssignableFrom(BigInteger.class))
537                     return BigInteger.valueOf(d.getTime());
538                 else
539                     return o;
540             } else if (o instanceof java.sql.Timestamp) {
541                 Timestamp t = (java.sql.Timestamp) o;
542                 if (cls.isAssignableFrom(Long.class))
543                     return t.getTime();
544                 else if (cls.isAssignableFrom(BigInteger.class))
545                     return BigInteger.valueOf(t.getTime());
546                 else
547                     return o;
548             } else if (o instanceof java.sql.Time) {
549                 Time t = (java.sql.Time) o;
550                 if (cls.isAssignableFrom(Long.class))
551                     return t.getTime();
552                 else if (cls.isAssignableFrom(BigInteger.class))
553                     return BigInteger.valueOf(t.getTime());
554                 else
555                     return o;
556             } else if (o instanceof Blob && cls.isAssignableFrom(byte[].class)) {
557                 return toBytes((Blob) o);
558             } else if (o instanceof Clob && cls.isAssignableFrom(String.class)) {
559                 return toString((Clob) o);
560             } else if (o instanceof BigInteger && cls.isAssignableFrom(Long.class)) {
561                 return ((BigInteger) o).longValue();
562             } else if (o instanceof BigInteger && cls.isAssignableFrom(Integer.class)) {
563                 return ((BigInteger) o).intValue();
564             } else if (o instanceof BigInteger && cls.isAssignableFrom(Double.class)) {
565                 return ((BigInteger) o).doubleValue();
566             } else if (o instanceof BigInteger && cls.isAssignableFrom(Float.class)) {
567                 return ((BigInteger) o).floatValue();
568             } else if (o instanceof BigInteger && cls.isAssignableFrom(Short.class)) {
569                 return ((BigInteger) o).shortValue();
570             } else if (o instanceof BigInteger && cls.isAssignableFrom(BigDecimal.class)) {
571                 return new BigDecimal((BigInteger) o);
572             } else if (o instanceof BigDecimal && cls.isAssignableFrom(Double.class)) {
573                 return ((BigDecimal) o).doubleValue();
574             } else if (o instanceof BigDecimal && cls.isAssignableFrom(Integer.class)) {
575                 return ((BigDecimal) o).toBigInteger().intValue();
576             } else if (o instanceof BigDecimal && cls.isAssignableFrom(Float.class)) {
577                 return ((BigDecimal) o).floatValue();
578             } else if (o instanceof BigDecimal && cls.isAssignableFrom(Short.class)) {
579                 return ((BigDecimal) o).toBigInteger().shortValue();
580             } else if (o instanceof BigDecimal && cls.isAssignableFrom(Long.class)) {
581                 return ((BigDecimal) o).toBigInteger().longValue();
582             } else if (o instanceof BigDecimal && cls.isAssignableFrom(BigInteger.class)) {
583                 return ((BigDecimal) o).toBigInteger();
584             } else if ((o instanceof Short || o instanceof Integer || o instanceof Long)
585                     && cls.isAssignableFrom(BigInteger.class)) {
586                 return new BigInteger(o.toString());
587             } else if (o instanceof Number && cls.isAssignableFrom(BigDecimal.class)) {
588                 return new BigDecimal(o.toString());
589             } else if (o instanceof Number && cls.isAssignableFrom(Short.class))
590                 return ((Number) o).shortValue();
591             else if (o instanceof Number && cls.isAssignableFrom(Integer.class))
592                 return ((Number) o).intValue();
593             else if (o instanceof Number && cls.isAssignableFrom(Integer.class))
594                 return ((Number) o).intValue();
595             else if (o instanceof Number && cls.isAssignableFrom(Long.class))
596                 return ((Number) o).longValue();
597             else if (o instanceof Number && cls.isAssignableFrom(Float.class))
598                 return ((Number) o).floatValue();
599             else if (o instanceof Number && cls.isAssignableFrom(Double.class))
600                 return ((Number) o).doubleValue();
601             else
602                 return o;
603         }
604     }
605 
606     public static <T> Object mapObject(final ResultSet rs, Class<T> cls, int i) {
607         return autoMap(getObject(rs, cls, i), cls);
608     }
609 
610     private static <T> Object getObject(final ResultSet rs, Class<T> cls, int i) {
611         try {
612             if (rs.getObject(i) == null) {
613                 return null;
614             }
615 
616             final int type = rs.getMetaData().getColumnType(i);
617             // TODO java.util.Calendar support
618             // TODO XMLGregorian Calendar support
619             if (type == Types.DATE)
620                 return rs.getDate(i, Calendar.getInstance());
621             else if (type == Types.TIME)
622                 return rs.getTime(i, Calendar.getInstance());
623             else if (type == Types.TIMESTAMP)
624                 return rs.getTimestamp(i, Calendar.getInstance());
625             else if (type == Types.CLOB && cls.equals(String.class)) {
626                 return toString(rs.getClob(i));
627             } else if (type == Types.CLOB && Reader.class.isAssignableFrom(cls)) {
628                 Clob c = rs.getClob(i);
629                 Reader r = c.getCharacterStream();
630                 return createFreeOnCloseReader(c, r);
631             } else if (type == Types.BLOB && cls.equals(byte[].class)) {
632                 return toBytes(rs.getBlob(i));
633             } else if (type == Types.BLOB && InputStream.class.isAssignableFrom(cls)) {
634                 final Blob b = rs.getBlob(i);
635                 final InputStream is = rs.getBlob(i).getBinaryStream();
636                 return createFreeOnCloseInputStream(b, is);
637             } else
638                 return rs.getObject(i);
639         } catch (SQLException e) {
640             throw new SQLRuntimeException(e);
641         }
642     }
643 
644     /**
645      * Returns the bytes of a {@link Blob} and frees the blob resource.
646      * 
647      * @param b
648      *            blob
649      * @return
650      */
651     private static byte[] toBytes(Blob b) {
652         try {
653             InputStream is = b.getBinaryStream();
654             byte[] result = IOUtils.toByteArray(is);
655             is.close();
656             b.free();
657             return result;
658         } catch (IOException e) {
659             throw new RuntimeException(e);
660         } catch (SQLException e) {
661             throw new SQLRuntimeException(e);
662         }
663 
664     }
665 
666     /**
667      * Returns the String of a {@link Clob} and frees the clob resource.
668      * 
669      * @param c
670      * @return
671      */
672     private static String toString(Clob c) {
673         try {
674             Reader reader = c.getCharacterStream();
675             String result = IOUtils.toString(reader);
676             reader.close();
677             c.free();
678             return result;
679         } catch (IOException e) {
680             throw new RuntimeException(e);
681         } catch (SQLException e) {
682             throw new SQLRuntimeException(e);
683         }
684     }
685 
686     /**
687      * Automatically frees the blob (<code>blob.free()</code>) once the blob
688      * {@link InputStream} is closed.
689      * 
690      * @param blob
691      * @param is
692      * @return
693      */
694     private static InputStream createFreeOnCloseInputStream(final Blob blob, final InputStream is) {
695         return new InputStream() {
696 
697             @Override
698             public int read() throws IOException {
699                 return is.read();
700             }
701 
702             @Override
703             public void close() throws IOException {
704                 try {
705                     is.close();
706                 } finally {
707                     try {
708                         blob.free();
709                     } catch (SQLException e) {
710                         log.debug(e.getMessage());
711                     }
712                 }
713             }
714         };
715     }
716 
717     /**
718      * Automatically frees the clob (<code>Clob.free()</code>) once the clob
719      * Reader is closed.
720      * 
721      * @param clob
722      * @param reader
723      * @return
724      */
725     private static Reader createFreeOnCloseReader(final Clob clob, final Reader reader) {
726         return new Reader() {
727 
728             @Override
729             public void close() throws IOException {
730                 try {
731                     reader.close();
732                 } finally {
733                     try {
734                         clob.free();
735                     } catch (SQLException e) {
736                         log.debug(e.getMessage());
737                     }
738                 }
739             }
740 
741             @Override
742             public int read(char[] cbuf, int off, int len) throws IOException {
743                 return reader.read(cbuf, off, len);
744             }
745         };
746     }
747 
748     /**
749      * Sets parameters for the {@link PreparedStatement}.
750      * 
751      * @param ps
752      * @param params
753      * @throws SQLException
754      */
755     static void setParameters(PreparedStatement ps, List<Parameter> params, boolean namesAllowed)
756             throws SQLException {
757         for (int i = 1; i <= params.size(); i++) {
758             if (params.get(i - 1).hasName() && !namesAllowed)
759                 throw new SQLException("named parameter found but sql does not contain names");
760             Object o = params.get(i - 1).value();
761             try {
762                 if (o == null)
763                     ps.setObject(i, null);
764                 else if (o == Database.NULL_CLOB)
765                     ps.setNull(i, Types.CLOB);
766                 else if (o == Database.NULL_BLOB)
767                     ps.setNull(i, Types.BLOB);
768                 else {
769                     Class<?> cls = o.getClass();
770                     if (Clob.class.isAssignableFrom(cls)) {
771                         setClob(ps, i, o, cls);
772                     } else if (Blob.class.isAssignableFrom(cls)) {
773                         setBlob(ps, i, o, cls);
774                     } else if (Calendar.class.isAssignableFrom(cls)) {
775                         Calendar cal = (Calendar) o;
776                         Timestamp t = new java.sql.Timestamp(cal.getTimeInMillis());
777                         ps.setTimestamp(i, t, cal);
778                     } else if (Time.class.isAssignableFrom(cls)) {
779                         Calendar cal = Calendar.getInstance();
780                         ps.setTime(i, (Time) o, cal);
781                     } else if (Timestamp.class.isAssignableFrom(cls)) {
782                         Calendar cal = Calendar.getInstance();
783                         ps.setTimestamp(i, (Timestamp) o, cal);
784                     } else if (java.sql.Date.class.isAssignableFrom(cls)) {
785                         Calendar cal = Calendar.getInstance();
786                         ps.setDate(i, (java.sql.Date) o, cal);
787                     } else if (java.util.Date.class.isAssignableFrom(cls)) {
788                         Calendar cal = Calendar.getInstance();
789                         java.util.Date date = (java.util.Date) o;
790                         ps.setTimestamp(i, new java.sql.Timestamp(date.getTime()), cal);
791                     } else
792                         ps.setObject(i, o);
793                 }
794             } catch (SQLException e) {
795                 log.debug("{} when setting ps.setObject({},{})", e.getMessage(), i, o);
796                 throw e;
797             }
798         }
799     }
800 
801     /**
802      * Sets a blob parameter for the prepared statement.
803      * 
804      * @param ps
805      * @param i
806      * @param o
807      * @param cls
808      * @throws SQLException
809      */
810     private static void setBlob(PreparedStatement ps, int i, Object o, Class<?> cls)
811             throws SQLException {
812         final InputStream is;
813         if (o instanceof byte[]) {
814             is = new ByteArrayInputStream((byte[]) o);
815         } else if (o instanceof InputStream)
816             is = (InputStream) o;
817         else
818             throw new RuntimeException("cannot insert parameter of type " + cls
819                     + " into blob column " + i);
820         Blob c = ps.getConnection().createBlob();
821         OutputStream os = c.setBinaryStream(1);
822         copy(is, os);
823         ps.setBlob(i, c);
824     }
825 
826     /**
827      * Sets the clob parameter for the prepared statement.
828      * 
829      * @param ps
830      * @param i
831      * @param o
832      * @param cls
833      * @throws SQLException
834      */
835     private static void setClob(PreparedStatement ps, int i, Object o, Class<?> cls)
836             throws SQLException {
837         final Reader r;
838         if (o instanceof String)
839             r = new StringReader((String) o);
840         else if (o instanceof Reader)
841             r = (Reader) o;
842         else
843             throw new RuntimeException("cannot insert parameter of type " + cls
844                     + " into clob column " + i);
845         Clob c = ps.getConnection().createClob();
846         Writer w = c.setCharacterStream(1);
847         copy(r, w);
848         ps.setClob(i, c);
849     }
850 
851     /**
852      * Copies a {@link Reader} to a {@link Writer}.
853      * 
854      * @param input
855      * @param output
856      * @return
857      */
858     private static int copy(Reader input, Writer output) {
859         try {
860             return IOUtils.copy(input, output);
861         } catch (IOException e) {
862             throw new RuntimeException(e);
863         }
864     }
865 
866     /**
867      * Copies an {@link InputStream} to an {@link OutputStream}.
868      * 
869      * @param input
870      * @param output
871      * @return
872      */
873     private static int copy(InputStream input, OutputStream output) {
874         try {
875             return IOUtils.copy(input, output);
876         } catch (IOException e) {
877             throw new RuntimeException(e);
878         }
879     }
880 
881     /**
882      * Returns a function that reads a {@link Reader} into a String.
883      */
884     public static final Func1<Reader, String> READER_TO_STRING = new Func1<Reader, String>() {
885         @Override
886         public String call(Reader r) {
887             try {
888                 return IOUtils.toString(r);
889             } catch (IOException e) {
890                 throw new RuntimeException(e);
891             }
892         }
893     };
894 
895     // Lazy singleton
896     private static final class ResultSetMapperToOne {
897         static final ResultSetMapper<Integer> INSTANCE = new ResultSetMapper<Integer>() {
898             @Override
899             public Integer call(ResultSet rs) {
900                 return 1;
901             }
902         };
903     }
904 
905     static ResultSetMapper<Integer> toOne() {
906         return ResultSetMapperToOne.INSTANCE;
907     }
908 
909     public static void setNamedParameters(PreparedStatement ps, List<Parameter> parameters,
910             List<String> names) throws SQLException {
911         Map<String, Parameter> map = new HashMap<String, Parameter>();
912         for (Parameter p : parameters) {
913             if (p.hasName()) {
914                 map.put(p.name(), p);
915             } else {
916                 throw new SQLException(
917                         "named parameters were expected but this parameter did not have a name: "
918                                 + p);
919             }
920         }
921         List<Parameter> list = new ArrayList<Parameter>();
922         for (String name : names) {
923             if (!map.containsKey(name))
924                 throw new SQLException("named parameter is missing for '" + name + "'");
925             Parameter p = map.get(name);
926             list.add(p);
927         }
928         Util.setParameters(ps, list, true);
929     }
930 
931     static void setParameters(PreparedStatement ps, List<Parameter> parameters, List<String> names)
932             throws SQLException {
933         if (names.isEmpty()) {
934             Util.setParameters(ps, parameters, false);
935         } else {
936             Util.setNamedParameters(ps, parameters, names);
937         }
938     }
939 }