001package gu.sql2java;
002
003import java.sql.Array;
004import java.sql.Blob;
005import java.sql.Clob;
006import java.sql.Connection;
007import java.sql.DatabaseMetaData;
008
009import static com.google.common.base.Preconditions.*;
010import static gu.sql2java.SimpleLog.*;
011
012import java.math.BigDecimal;
013import java.net.URL;
014import java.nio.ByteBuffer;
015import java.sql.PreparedStatement;
016import java.sql.Ref;
017import java.sql.ResultSet;
018import java.sql.SQLException;
019import java.sql.Statement;
020import java.sql.Time;
021import java.sql.Timestamp;
022import java.sql.Types;
023import java.text.ParseException;
024import java.text.SimpleDateFormat;
025import java.util.Calendar;
026import java.util.Collections;
027import java.util.EnumMap;
028import java.util.List;
029import java.util.Map;
030import java.util.Map.Entry;
031import java.util.Properties;
032import java.util.StringTokenizer;
033import java.util.concurrent.Callable;
034import javax.sql.DataSource;
035
036import com.google.common.base.Function;
037import com.google.common.base.Strings;
038import com.google.common.collect.ImmutableMap;
039import com.google.common.collect.Lists;
040import com.mchange.v2.c3p0.ComboPooledDataSource;
041import com.mchange.v2.c3p0.DataSources;
042
043import gu.sql2java.exception.DaoException;
044import gu.sql2java.exception.DataAccessException;
045import gu.sql2java.exception.RuntimeDaoException;
046
047
048/**
049 * The Manager provides connections and manages transactions transparently.
050 * <br>
051 * It is a singleton, you get its instance with the getInstance() method.
052 * All of the XxxxManager classes use the Manager to get database connections.
053 * Before doing any operation, you must pass either a
054 * datasource or a jdbc driver/url/username/password.
055 * @author guyadong
056 */
057public final class Manager implements Constant,SqlRunner
058{
059    private static class Singleton{
060        private static final Manager INSTANCE = new Manager();
061    }
062    private static InheritableThreadLocal<Connection> transactionConnection = new InheritableThreadLocal<Connection>();
063    /** JDBC properties from properties file */
064    private static Properties databaseProperties;
065    
066    static{
067        String envVar="config_folder";
068        String propFile="database.properties";
069        String confFolder="conf";
070        databaseProperties = ConfigUtils.loadAllProperties(propFile, confFolder, envVar, Manager.class, false);
071    }
072    /**
073     * inject properties to {@link #databaseProperties}<br>
074     * be effected only while called before initializing singleton instance 
075     * @param properties
076     * @see JdbcProperty
077     */
078    public static final void injectProperties(Map<String,String> properties){
079        if(null != properties){
080            EnumMap<JdbcProperty, String> enumMap = new EnumMap<JdbcProperty,String>(JdbcProperty.class);
081            JdbcProperty property;
082            for(Entry<String, String> entry:properties.entrySet()){
083                if(null != (property = JdbcProperty.fromKey(entry.getKey()))){
084                    enumMap.put(property, entry.getValue());
085                }
086            }
087            injectProperties(enumMap);
088        }
089    }
090    /**
091     * inject properties to {@link #databaseProperties}<br>
092     * be effected only while called before initializing singleton instance 
093     * @param properties
094     */
095    public static final void injectProperties(EnumMap<JdbcProperty,String> properties){
096        if(null != properties){
097            Boolean isDebug = false;
098            if(properties.containsKey(JdbcProperty.DEBUG)){
099                isDebug = "true".equalsIgnoreCase(properties.get(JdbcProperty.DEBUG));
100                databaseProperties.setProperty(JdbcProperty.DEBUG.key, isDebug.toString());
101            }
102            String prefix=isDebug?"debug.":"work.";
103            String value;
104            JdbcProperty key;
105            for(Entry<JdbcProperty, String> entry : properties.entrySet()){
106                value = entry.getValue();
107                key = entry.getKey();
108                if( key != JdbcProperty.DEBUG && null !=value && !value.isEmpty()){
109                    databaseProperties.setProperty(key.withPrefix(prefix), value);
110                }
111            }
112        }
113    }
114    private volatile DataSource dataSource = null;
115    private String jdbcDriver = null;
116    private String jdbcUrl = null;
117    private String jdbcUsername = null;
118    private String jdbcPassword = null;
119    private volatile String generatedkeyRetrieve = null;
120    private volatile String generatedkeyStatement = null;
121    private volatile AutoKeyRetrieveType  retrieveType;
122    private String maxIdleTime;
123    private String idleConnectionTestPeriod;
124    private String maxPoolSize;
125    private String minPoolSize;
126    private boolean isDebug = false;
127    
128    /**
129     * Returns the manager singleton instance.
130     */
131    private Manager()
132    {
133        loadProperties(databaseProperties);
134    }
135
136    /**
137     * Returns the manager singleton instance.
138     */
139    public static Manager getInstance()
140    {
141        return Singleton.INSTANCE;
142    }
143    
144    /** dispose pool */
145    public void disposePool(){
146        try{
147            DataSources.destroy(dataSource);
148        }catch (Exception e) {
149            log("dispose pool wrong ..." + e);
150        }
151    }
152    
153    /**
154     * configure with the parameters given in the properties object
155     * @param properties the properties object to be used
156     */
157    public void loadProperties(Properties properties){
158        isDebug = Boolean.valueOf(properties.getProperty("isDebug"));
159        String prefix=isDebug ? "debug." : "work.";
160        jdbcDriver = properties.getProperty(JdbcProperty.JDBC_DRIVER.withPrefix(prefix));
161        jdbcUrl = properties.getProperty(JdbcProperty.JDBC_URL.withPrefix(prefix));
162        jdbcUsername = properties.getProperty(JdbcProperty.JDBC_USERNAME.withPrefix(prefix));
163        jdbcPassword = properties.getProperty(JdbcProperty.JDBC_PASSWORD.withPrefix(prefix));
164        
165        generatedkeyRetrieve = properties.getProperty(JdbcProperty.GENERATEDKEY_RETRIEVE.name());
166        generatedkeyStatement = properties.getProperty(JdbcProperty.GENERATEDKEY_STATEMENT.name());
167        
168        maxPoolSize = properties.getProperty(JdbcProperty.C3P0_MAXPOOLSIZE.withPrefix(prefix));
169        minPoolSize = properties.getProperty(JdbcProperty.C3P0_MINPOOLSIZE.withPrefix(prefix));
170        maxIdleTime = properties.getProperty(JdbcProperty.C3P0_MAXIDLETIME.withPrefix(prefix));
171        idleConnectionTestPeriod = properties.getProperty(JdbcProperty.C3P0_IDLECONNECTIONTESTPERIOD.withPrefix(prefix));
172    }
173    private void logDatabaseParameters(){
174        if(isDebug){
175            log("database using debug environment parameter: ");
176            log("jdbcUrl = " + jdbcUrl);
177            log("jdbcUsername = " + jdbcUsername);
178            log("jdbcPassword = " + jdbcPassword);
179            log("maxPoolSize = " + maxPoolSize);
180            log("minPoolSize = " + minPoolSize);
181            log("maxIdleTime = " + maxIdleTime);
182            log("idleConnectionTestPeriod = " + idleConnectionTestPeriod);
183        }
184    }    
185    /**
186     * use key synchronized to be sure the ds created once 
187     * @return 
188     */
189    private DataSource getDataSource(){
190        // double check
191        if (dataSource == null){
192                synchronized (this) {
193                        if (dataSource == null){
194                                try{
195                                        logDatabaseParameters();
196                                        //set C3P0 properties
197                                        ComboPooledDataSource cpds = new ComboPooledDataSource();                
198                                        cpds.setDriverClass(jdbcDriver);
199                                        cpds.setUser(jdbcUsername);
200                                        cpds.setPassword(jdbcPassword);
201                                        cpds.setJdbcUrl(jdbcUrl);
202                                        cpds.setMaxPoolSize(Integer.parseInt(maxPoolSize));
203                                        cpds.setMinPoolSize(Integer.parseInt(minPoolSize));
204                                        cpds.setMaxIdleTime(Integer.parseInt(maxIdleTime));
205                                        cpds.setIdleConnectionTestPeriod(Integer.parseInt(idleConnectionTestPeriod));
206                                        dataSource = cpds; 
207                                }catch (Exception e){
208                                        throw new IllegalArgumentException(String.format("can't get connection by argument...driver/url/username/password[%s/%s/%s/%s]",jdbcDriver,jdbcUrl,jdbcUsername,jdbcPassword),e);
209                                }
210                        }
211                }
212        }
213        return dataSource;
214    }
215    
216    /**
217     * Gets an auto commit connection.
218     * <br>
219     * Normally you do not need this method that much ;-)
220     *
221     * @return an auto commit connection
222     */
223    public Connection getConnection() throws SQLException
224    {
225        Connection tc = transactionConnection.get();
226        if (tc != null) {
227            return tc;
228        }
229        return getDataSource().getConnection();
230    }
231
232    /**
233     * Releases the database connection.
234     * <br>
235     * Normally you should not need this method ;-)
236     */
237    public void releaseConnection(Connection c)
238    {
239        Connection tc = transactionConnection.get();
240        if (tc != null){
241            return;
242        }
243        try{
244            if (c != null){
245                c.close();
246            }
247        }catch (SQLException x){
248            log("Could not release the connection: "+x.toString());
249        }
250    }
251
252    /**
253     * Initiates a database transaction.
254     * <br>
255     * When working within a transaction, you should invoke this method first.
256     * The connection is returned just in case you need to set the isolation level.
257     *
258     * @return a non-auto commit connection with the default transaction isolation level
259     */
260    public Connection beginTransaction() throws SQLException
261    {
262        Connection c = this.getConnection();
263        c.setAutoCommit(false);
264        transactionConnection.set(c);
265        return c;
266    }
267
268    /**
269     * Releases connection used for the transaction and performs a commit or rollback.
270     *
271     * @param commit tells whether this connection should be committed
272     *        true for commit(), false for rollback()
273     */
274    public void endTransaction(boolean commit) throws SQLException
275    {
276        Connection c = transactionConnection.get();
277        if (c == null)
278        {
279            return;
280        }
281
282        try
283        {
284            if (commit)
285            {
286                c.commit();
287            }
288            else
289            {
290                c.rollback();
291            }
292        }
293        finally
294        {
295            c.setAutoCommit(true);
296            transactionConnection.set(null);
297            releaseConnection(c);
298        }
299    }
300    /**
301     * Run {@code Callable<T>} as a transaction.<br>
302     * all exceptions but {@code SQLException} threw by {@code Callable<T>} is wrapped into {@code RuntimeException}<br>
303     * throw {@code NullPointerException} if {@code fun} be {@code null}<br>
304     * @param <T>  type of return result
305     * @param fun
306     * @param transactionListener listener for transaction status
307     * @return
308     * @throws RuntimeDaoException
309     * @see #beginTransaction()
310     * @see #endTransaction(boolean)
311     */
312    <T>T runAsTransaction(Callable<T> fun,TransactionListener transactionListener) throws RuntimeDaoException{
313        checkArgument(fun != null, "fun is null");
314        checkArgument(null != transactionListener,"transactionListener is null");
315        try {
316            beginTransaction();
317            transactionListener.onBegin();
318            boolean commit = false;
319            try {
320                T result = fun.call();
321                commit = true;
322                transactionListener.onCommit();
323                return result;
324            } catch (SQLException e) {
325                throw e;
326            } catch (RuntimeException e) {
327                throw e;
328            }catch (Exception e) {
329                throw new RuntimeException(e);
330            }finally {
331                endTransaction(commit);
332                transactionListener.onEnd();
333            }
334        } catch (SQLException e) {
335            throw new RuntimeDaoException(e);
336        }
337    }
338    
339    /**
340         * Run {@code Runnable} as a transaction.no return
341         * @param fun
342         * @param transactionListener listener for transaction status
343         * @throws RuntimeDaoException
344         * @see #runAsTransaction(Callable,TableListener.TransactionListener)
345         */
346        void runAsTransaction(final Runnable fun,TransactionListener transactionListener) throws RuntimeDaoException{
347                checkArgument(fun != null, "fun is null");
348            runAsTransaction(new Callable<Object>(){
349        
350                @Override
351                public Object call() throws Exception {
352                    fun.run();
353                    return null;
354                }},transactionListener);
355        }
356        /**
357     * @param fun
358     * @return
359     * @throws RuntimeDaoException
360     */
361    @Override
362    public <T>T runAsTransaction(Callable<T> fun) throws RuntimeDaoException{
363        return runAsTransaction(fun, ListenerContainer.TRANSACTION_LISTENER);
364    }
365    /**
366     * Run {@code Runnable} as a transaction.no return
367     * @param fun
368     * @throws DaoException
369     */
370    @Override
371    public void runAsTransaction(final Runnable fun) throws RuntimeDaoException{
372        runAsTransaction(fun,ListenerContainer.TRANSACTION_LISTENER);
373    }
374
375////////////////////////////////////////////////////
376// cleaning method
377////////////////////////////////////////////////////
378
379    /**
380     * Closes the passed Statement.
381     */
382    void close(Statement s)
383    {
384        try
385        {
386            if (s != null) {
387                s.close();
388            }
389        }
390        catch (SQLException x)
391        {
392            log("Could not close statement!: " + x.toString());
393        }
394    }
395
396    /**
397     * Closes the passed ResultSet.
398     */
399    void close(ResultSet rs)
400    {
401        try
402        {
403            if (rs != null) {
404                rs.close();
405            }
406        }
407        catch (SQLException x)
408        {
409            log("Could not close result set!: " + x.toString());
410        }
411    }
412
413    /**
414     * Closes the passed Statement and ResultSet.
415     */
416    void close(Statement s, ResultSet rs)
417    {
418        close(rs);
419        close(s);
420    }
421
422        ////////////////////////////////////////////////////
423        // Helper methods for fetching numbers using IDs or names
424        ////////////////////////////////////////////////////
425
426    /**
427     * return all bytes in buffer (position~limit),no change status of buffer
428     * @param buffer
429     * @return
430     */
431    private static final byte[] getBytesInBuffer(ByteBuffer buffer){
432        if(null == buffer){
433                return null;
434        }
435        int pos = buffer.position();
436        try{
437            byte[] bytes = new byte[buffer.remaining()];
438            buffer.get(bytes);
439            return bytes;
440        }finally{
441            buffer.position(pos);
442        }
443    }
444    /**
445     * Set a byte array to the passed prepared statement as a ByteBuffer or as null.
446     */
447    private static void  setBytes(int sqlType,PreparedStatement ps, int pos, ByteBuffer bytes) throws SQLException
448    {
449        setBytes(sqlType,ps,pos,getBytesInBuffer(bytes));
450    }
451    /**
452     * Set a byte array to the passed prepared statement as a ByteBuffer or as null.
453     */
454    private static void  setBytes(int sqlType,PreparedStatement ps, int pos, byte[] bytes) throws SQLException
455    {
456        if (null == bytes){
457            ps.setNull(pos, sqlType);
458        }else{
459            ps.setBytes(pos, bytes);
460        }
461    }
462    /**
463     * Retrieves a date value from the passed result set as a Calendar object.
464     */
465    static Calendar getCalendar(ResultSet rs, int pos) throws SQLException
466    {
467        Timestamp date = rs.getTimestamp(pos);
468        if (rs.wasNull()) {
469            return null;
470        }
471        Calendar calendar = Calendar.getInstance();
472        calendar.setTime(date);
473        return calendar;
474    }
475
476    /**
477     * Retrieves a date value from the passed result set as a Calendar object.
478     */
479    static Calendar getCalendar(ResultSet rs, String column) throws SQLException
480    {
481        Timestamp date = rs.getTimestamp(column);
482        if (null == date) {
483            return null;
484        }
485        Calendar calendar = Calendar.getInstance();
486        calendar.setTime(date);
487        return calendar;
488    }
489
490    /**
491     * Set a Calendar object to the passed prepared statement as a date or as null.
492     */
493    static void  setCalendar(PreparedStatement ps, int pos, Calendar calendar) throws SQLException
494    {
495        if (calendar == null)
496        {
497            ps.setNull(pos, Types.TIMESTAMP);
498        }
499        else
500        {
501            ps.setTimestamp(pos, new java.sql.Timestamp(calendar.getTimeInMillis()));
502        }
503    }
504
505    /**
506     * 
507     * For compatibility with old version {@link ResultSet} without getObject(int,Class) method
508     * @param rs
509     * @param columnIndex
510     * @param type
511     * @return
512     * @throws SQLException
513     */
514    @SuppressWarnings("unchecked")
515    public static <T> T getObject(ResultSet rs,int columnIndex, Class<T> type) throws SQLException {
516        if (type == null) {
517            throw new SQLException("Type parameter can not be null");
518        }
519        Object t = null;
520        if (type.equals(String.class)) {
521            t = rs.getString(columnIndex);
522        } else if (type.equals(BigDecimal.class)) {
523            t =  rs.getBigDecimal(columnIndex);
524        } else if (type.equals(Boolean.class) || type.equals(Boolean.TYPE)) {
525            t =  Boolean.valueOf(rs.getBoolean(columnIndex));
526        } else if (type.equals(Integer.class) || type.equals(Integer.TYPE)) {
527            t =  Integer.valueOf(rs.getInt(columnIndex));
528        } else if (type.equals(Long.class) || type.equals(Long.TYPE)) {
529            t =  Long.valueOf(rs.getLong(columnIndex));
530        } else if (type.equals(Float.class) || type.equals(Float.TYPE)) {
531            t =  Float.valueOf(rs.getFloat(columnIndex));
532        } else if (type.equals(Double.class) || type.equals(Double.TYPE)) {
533            t =  Double.valueOf(rs.getDouble(columnIndex));
534        } else if (type.equals(byte[].class)) {
535            t =  rs.getBytes(columnIndex);
536        } else if (type.equals(ByteBuffer.class)) {
537                byte[] array = rs.getBytes(columnIndex);
538            t =  (array == null ? null : ByteBuffer.wrap(array));
539        } else if (type.equals(java.sql.Date.class)) {
540            t =  rs.getDate(columnIndex);
541        } else if (type.equals(Time.class)) {
542            t =  rs.getTime(columnIndex);
543        } else if (type.equals(Timestamp.class)) {
544            t =  rs.getTimestamp(columnIndex);
545        } else if (type.equals(Calendar.class)) {
546                java.util.Date date = (java.util.Date) rs.getObject(columnIndex);
547            if (date == null) {
548                return null;
549            }
550            Calendar calendar = Calendar.getInstance();
551            calendar.setTime(date);
552            t =  calendar;
553        } else if (type.equals(Clob.class)) {
554            t =  rs.getClob(columnIndex);
555        } else if (type.equals(Blob.class)) {
556            t =  rs.getBlob(columnIndex);
557        } else if (type.equals(Array.class)) {
558            t =  rs.getArray(columnIndex);
559        } else if (type.equals(Ref.class)) {
560            t =  rs.getRef(columnIndex);
561        } else if (type.equals(URL.class)) {
562            t =  rs.getURL(columnIndex);
563        } else {
564            try {
565                t =  type.cast(rs.getObject(columnIndex));
566            } catch (ClassCastException e) {
567                throw new SQLException("Conversion not supported for type " + type.getName(), e);
568            }
569        }
570        return rs.wasNull() ? null : (T)t;
571    }
572    
573////////////////////////////////////////////////////
574// Date helper methods
575////////////////////////////////////////////////////
576
577    /**
578     * pattern for received date processing.
579     */
580    private static final String[] PATTERNS = new String[]
581    {
582        "EEE, dd MMM yyyy HH:mm:ss '-'S '('z')'",
583        "EEE, dd MMM yyyy HH:mm:ss '+'S '('z')'",
584        "EEE, dd MMM yyyy HH:mm:ss '-'S",
585        "EEE, dd MMM yyyy HH:mm:ss '+'S",
586        "EEE, dd MMM yyyy HH:mm:ss z",
587        "EEE, dd MMM yyyy HH:mm:ss Z",
588        "EEE, dd MMM yyyy HH:mm:ss",
589        "EEE, d MMM yyyy HH:mm:ss '-'S '('z')'",
590        "EEE, d MMM yyyy HH:mm:ss '+'S '('z')'",
591        "EEE, d MMM yyyy HH:mm:ss '-'S",
592        "EEE, d MMM yyyy HH:mm:ss '+'S",
593        "EEE, d MMM yyyy HH:mm:ss z",
594        "EEE, d MMM yyyy HH:mm:ss Z",
595        "EEE, d MMM yyyy HH:mm:ss",
596
597        "EEE, dd MMM yy HH:mm:ss '-'S '('z')'",
598        "EEE, dd MMM yy HH:mm:ss '+'S '('z')'",
599        "EEE, dd MMM yy HH:mm:ss '-'S",
600        "EEE, dd MMM yy HH:mm:ss '+'S",
601        "EEE, dd MMM yy HH:mm:ss z",
602        "EEE, dd MMM yy HH:mm:ss Z",
603        "EEE, dd MMM yy HH:mm:ss",
604        "EEE, d MMM yy HH:mm:ss '-'S '('z')'",
605        "EEE, d MMM yy HH:mm:ss '+'S '('z')'",
606        "EEE, d MMM yy HH:mm:ss '-'S",
607        "EEE, d MMM yy HH:mm:ss '+'S",
608        "EEE, d MMM yy HH:mm:ss z",
609        "EEE, d MMM yy HH:mm:ss Z",
610        "EEE, d MMM yy HH:mm:ss",
611
612        "dd MMM yyyy HH:mm:ss '-'S",
613        "dd MMM yyyy HH:mm:ss '+'S",
614        "dd MMM yyyy HH:mm:ss '-'S '('z')'",
615        "dd MMM yyyy HH:mm:ss '+'S '('z')'",
616        "dd MMM yyyy HH:mm:ss z",
617        "dd MMM yyyy HH:mm:ss Z",
618        "dd MMM yyyy HH:mm:ss",
619
620        "dd MMM yyy HH:mm:ss '-'S",
621        "dd MMM yyy HH:mm:ss '+'S",
622        "dd MMM yyy HH:mm:ss '-'S '('z')'",
623        "dd MMM yyy HH:mm:ss '+'S '('z')'",
624        "dd MMM yyy HH:mm:ss z",
625        "dd MMM yyy HH:mm:ss Z",
626        "dd MMM yyy HH:mm:ss",
627
628        "yyyy.MM.dd HH:mm:ss z",
629        "yyyy.MM.dd HH:mm:ss Z",
630        "yyyy.MM.d HH:mm:ss z",
631        "yyyy.MM.d HH:mm:ss Z",
632        "yyyy.MM.dd HH:mm:ss",
633        "yyyy.MM.d HH:mm:ss",
634
635        "yy.MM.dd HH:mm:ss z",
636        "yy.MM.dd HH:mm:ss Z",
637        "yy.MM.d HH:mm:ss z",
638        "yy.MM.d HH:mm:ss Z",
639        "yy.MM.dd HH:mm:ss",
640        "yy.MM.d HH:mm:ss",
641
642        "yyyy MM dd HH:mm:ss",
643        "yyyy MM d HH:mm:ss",
644        "yyyy MM dd HH:mm:ss z",
645        "yyyy MM dd HH:mm:ss Z",
646        "yyyy MM d HH:mm:ss z",
647        "yyyy MM d HH:mm:ss Z",
648
649        "yy MM dd HH:mm:ss",
650        "yy MM d HH:mm:ss",
651        "yy MM dd HH:mm:ss z",
652        "yy MM dd HH:mm:ss Z",
653        "yy MM d HH:mm:ss z",
654        "yy MM d HH:mm:ss Z",
655
656        "yyyy-MM-dd HH:mm:ss z",
657        "yyyy-MM-dd HH:mm:ss Z",
658        "yyyy-MM-d HH:mm:ss z",
659        "yyyy-MM-d HH:mm:ss Z",
660        "yyyy-MM-dd HH:mm:ss",
661        "yyyy-MM-d HH:mm:ss",
662
663        "yy-MM-dd HH:mm:ss z",
664        "yy-MM-dd HH:mm:ss Z",
665        "yy-MM-d HH:mm:ss z",
666        "yy-MM-d HH:mm:ss Z",
667        "yy-MM-dd HH:mm:ss",
668        "yy-MM-d HH:mm:ss",
669
670        "dd MMM yyyy",
671        "d MMM yyyy",
672
673        "dd.MMM.yyyy",
674        "d.MMM.yyyy",
675
676        "dd-MMM-yyyy",
677        "d-MMM-yyyy",
678
679        "dd MM yyyy",
680        "d MM yyyy",
681
682        "dd.MM.yyyy",
683        "d.MM.yyyy",
684
685        "dd-MM-yyyy",
686        "d-MM-yyyy",
687
688        "yyyy MM dd",
689        "yyyy MM d",
690
691        "yyyy.MM.dd",
692        "yyyy.MM.d",
693
694        "yyyy-MM-dd",
695        "yyyy-MM-d",
696
697        "dd MMM yy",
698        "d MMM yy",
699
700        "dd.MMM.yy",
701        "d.MMM.yy",
702
703        "dd-MMM-yy",
704        "d-MMM-yy",
705
706        "dd MM yy",
707        "d MM yy",
708
709        "dd.MM.yy",
710        "d.MM.yy",
711
712        "dd-MM-yy",
713        "d-MM-yy",
714
715        "yy MMM dd",
716        "yy MMM d",
717
718        "yy.MMM.dd",
719        "yy.MMM.d",
720
721        "yy-MMM-dd",
722        "yy-MMM-d",
723
724        "yy MMM dd",
725        "yy MMM d",
726
727        "yy.MMM.dd",
728        "yy.MMM.d",
729
730        "yy-MMM-dd",
731        "yy-MMM-d",
732        // ex: Wed 19, Feb 2003
733        "EEE dd, MMM yyyy", 
734        // ex: Wed 19, Feb 03
735        "EEE dd, MMM yy" 
736    };
737
738
739    /**
740     * get a date from a date string representation in one of the registered formats
741     * @param strDate the date as string. 
742     * @return Date object ,otherwise null If (null or empty) or correct pattern was not found
743     */
744    static java.util.Date getDateFromString(String strDate)
745    {
746        java.util.Date dReceivedDate = null;
747        if (strDate == null) {
748            return dReceivedDate;
749        } else {
750            strDate = strDate.trim();
751        }
752
753        SimpleDateFormat pSimpleDateFormat = new SimpleDateFormat("");
754        if (!strDate.isEmpty())
755        {
756            for (int i=0; i<PATTERNS.length; i++)
757            {
758                try
759                {
760                    pSimpleDateFormat.applyPattern(PATTERNS[i]);
761                    dReceivedDate = pSimpleDateFormat.parse(strDate);
762                    if (dReceivedDate == null)
763                    {
764                        continue;
765                    }
766                    return dReceivedDate;
767                }
768                catch (ParseException pe)
769                {
770                    ; // ignore this format try the next one
771                }
772            }
773        }
774        return dReceivedDate;
775    }
776
777    /**
778     * Verify that the string represantes the date with one of the registered formats
779     * @param strDate the date as string.
780     * @return boolean "true" if the string represantes the date in one of the registed formats.
781     */
782    static boolean isDate(String strDate)
783    {
784        return null != getDateFromString(strDate);
785    }
786    
787    static String buildProcedureCall(String packageName, String procedureName, int paramCount) {
788        return buildProcedureCall(packageName + "." + procedureName, paramCount);
789    }
790    
791    static String buildProcedureCall(String procedureName, int paramCount) {
792        StringBuilder sb = new StringBuilder("{call ").append(procedureName).append("(");
793        for (int n = 1; n <= paramCount; n++) {
794            sb.append("?,");
795        }
796        if (paramCount > 0) {
797            sb.setLength(sb.length()-1);
798        }
799        return sb.append(")}").toString();
800    }
801    List<BaseBean> runPreparedStatementAsList(Map<String, Class<?>> targetTypes,
802                PreparedStatement ps){
803        checkArgument(null !=ps,"ps is null");
804        ResultSet rs = null;
805        try {
806            ps.setFetchSize(100);
807            rs = ps.executeQuery();
808            UnnameRowMetaData metaData = new UnnameRowMetaData(rs.getMetaData(), targetTypes);
809            List<BaseBean> list = Lists.newLinkedList();
810            while(rs.next()){
811                BaseBean row = new UnnameRow(metaData);
812                for(int i=0; i<metaData.defaultColumnIdList.length; ++i){
813                        row.setValue(i, rs.getObject(i+1));
814                }
815                list.add(row);
816            }
817            return list;
818        } catch (DaoException e) {
819            throw new RuntimeDaoException(e);
820        }catch (SQLException e) {
821            throw new RuntimeDaoException(new DataAccessException(e));
822        } finally {
823            this.close(rs);
824        }
825    }
826    List<BaseBean> runSqlAsList(Map<String, Class<?>> targetTypes, String sql, Object... argList){
827        checkArgument(!Strings.isNullOrEmpty(sql),"sql is null or empty");
828        PreparedStatement ps = null;
829        Connection connection = null;
830        try {
831            connection = this.getConnection();
832            if(isDebug){
833                log("runSqlAsList:" + sql);
834            }
835            ps = connection.prepareStatement(sql,
836                    ResultSet.TYPE_FORWARD_ONLY,
837                    ResultSet.CONCUR_READ_ONLY);
838            fillPrepareStatement(ps, argList);
839            return runPreparedStatementAsList(targetTypes,ps);
840        } catch (DaoException e) {
841            throw new RuntimeDaoException(e);
842        }catch (SQLException e) {
843            throw new RuntimeDaoException(new DataAccessException(e));
844        } finally {
845            this.close(ps);
846            this.releaseConnection(connection);
847        }
848    }
849    
850    private static Function<BaseBean,Map<String,Object>> VMAP_FUN = new Function<BaseBean,Map<String,Object>>(){
851
852                @Override
853                public Map<String, Object> apply(BaseBean input) {
854                        return input.asNameValueMap();
855                }};
856
857        @Override
858    public List<Map<String, Object>> runSqlForMap(Map<String,Class<?>> targetType, String sql,Object... argList) throws RuntimeDaoException{
859        List<BaseBean> list = runSqlAsList(targetType, sql, argList);
860        if(list.isEmpty()){
861                return Collections.emptyList();
862        }
863                return Lists.transform(list, VMAP_FUN);
864    }
865    
866    private <T> List<T> fetchOnlyOneColumn(List<BaseBean> list,final Class<T> targetType){
867        if(list.isEmpty()){
868                return Collections.emptyList();
869        }
870        BaseBean row = list.get(0);
871        Object[] values = row.asValueArray();
872        
873        checkArgument(values.length == 1,"more than  1 column returned");
874        return Lists.transform(list, new Function<BaseBean, T>() {
875                        @Override
876                        public T apply(BaseBean input) {
877                                if(null == targetType){
878                                        return input.getValue(0);
879                                }
880                                return targetType.cast(input.getValue(0));
881                        }
882                });
883    }
884    <T> List<T> runPreparedStatementAsList(Class<T> targetType, PreparedStatement ps) throws RuntimeDaoException{
885        List<BaseBean> list = runPreparedStatementAsList((Map<String,Class<?>>)null, ps);
886        return fetchOnlyOneColumn(list,targetType);
887    }
888    
889    @Override
890        public List<BaseBean> runSqlAsList(String sql, Object... argList) throws RuntimeDaoException{
891                return runSqlAsList(Collections.<String,Class<?>>emptyMap(), sql, argList);
892        }
893        @Override
894    public <T> List<T> runSqlAsList(Class<T> targetType, String sql, Object... argList) throws RuntimeDaoException{
895        List<BaseBean> list = runSqlAsList((Map<String,Class<?>>)null, sql, argList);
896        return fetchOnlyOneColumn(list,targetType);
897    }
898    
899    @Override
900    public <T> T runSqlForValue(Class<T> targetType,String sql, Object... argList) throws RuntimeDaoException{
901        List<T> list = runSqlAsList(targetType,sql,argList);
902        checkArgument(list.size() == 1,"more than  1 row returned for SQL: %s",sql);
903        return list.get(0);
904    }
905    <T> T runPreparedStatementForValue(Class<T> targetType,PreparedStatement ps) throws RuntimeDaoException{
906        List<T> list = runPreparedStatementAsList(targetType,ps);
907        checkArgument(list.size() == 1,"more than  1 row returned");
908        return list.get(0);
909    }
910        /**
911     * 填充PreparedStatement中的参数对象
912     * 
913     * @param ps
914     * @param argList
915     * @throws SQLException
916     */
917    protected static void fillPrepareStatement(PreparedStatement ps, Object[] argList) throws DaoException{
918        try {
919            if (!(argList == null || ps == null)) {
920                for (int i = 0; i < argList.length; i++) {
921                        if (argList[i] instanceof ByteBuffer) { 
922                        ps.setBytes(i + 1, getBytesInBuffer((ByteBuffer) argList[i]));
923                    } else {
924                        ps.setObject(i + 1, argList[i]);
925                    }
926                }
927            }
928        } catch (SQLException e) {
929            throw new DaoException(e);
930        }
931    }
932    protected static void setPreparedStatement(PreparedStatement ps,int pos,Object value,int sqlType)
933                        throws SQLException {           
934                if(value instanceof ByteBuffer){
935                        setBytes(sqlType, ps, pos, (ByteBuffer)value);
936                }else{
937                        ps.setObject(pos,value,sqlType);
938                }
939        }
940        
941        
942        /**
943         * For those who do not want to read below, please simply pick up the
944         * configuration associated with your database.
945         * 
946         * More explanation:
947         * When you save a bean whose primary key is numeric and has no value set,
948         * we assume that you want sql2java to retrieve a key's value generated
949         * on the database side.
950         * 
951         * generatedkey.retrieve can take 3 values:
952         * 
953         * auto - the standard approach when you have a JDBC 3.0 driver.
954         * 
955         * before - the key's value is retrieved before inserting the record.
956         * 
957         * after - the key's value is retrieved after inserting the record 
958         * @author guyadong
959         *
960         */
961        enum AutoKeyRetrieveType{
962                auto,after,before
963        }
964        /**
965         * 
966         * For those who do not want to read below, please simply pick up the
967         * configuration associated with your database.
968         * 
969         * More explanation:
970         * When you save a bean whose primary key is numeric and has no value set,
971         * we assume that you want sql2java to retrieve a key's value generated
972         * on the database side.
973         * 
974         * If you set it to before or after you also need to configure the
975         * autogeneratedkey.statement properties.
976         * <TABLE> is replaced at code generation time by the table name.
977         * <KEY> is replaced at code generation time by the column name.
978         * You may adjust this properties to fit your own naming convention.
979         * 
980         * PICK THE CONFIGURATION ASSOCIATED WITH YOUR DATABASE
981         * (or create one, but in that case let us know so we can add it here... :-)
982         */
983        private static final ImmutableMap<String, String> AUTOINC_MECHANISM = ImmutableMap.<String, String>builder()
984                /*-- HSQL ------*/
985                .put("hsql.generatedkey.retrieve","after")
986                .put("hsql.generatedkey.statement","CALL IDENTITY()")
987                /*-- ORACLE 8i ------*/
988                .put("oracle.generatedkey.retrieve","before")
989                .put("oracle.generatedkey.statement","SELECT SEQ_<TABLE>.nextval FROM DUAL")
990                /*-- MaxDB (SapDB) ------*/
991                .put("maxdb.generatedkey.retrieve","before")
992                .put("maxdb.generatedkey.statement","SELECT SEQ_<TABLE>_<KEY>.nextval FROM DUAL")
993                /*-- MYSQL (without jdbc 3.0 driver) ------*/
994                .put("mysql.generatedkey.retrieve","after")
995                .put("mysql.generatedkey.statement","SELECT last_insert_id()")
996                /*-- SQL SERVER ------*/
997                .put("sqlserver.generatedkey.retrieve","after")
998                .put("sqlserver.generatedkey.statement","SELECT @@IDENTITY")
999                /*-- SYBASE ------*/
1000                .put("sybase.generatedkey.retrieve","after")
1001                .put("sybase.generatedkey.statement","SELECT @@IDENTITY")
1002                /*-- POSTGRESQL ------*/
1003                .put("postgresql.generatedkey.retrieve","before")
1004                .put("postgresql.generatedkey.statement","SELECT nextval('<TABLE>_<KEY>_SEQ')")
1005                /*-- INFORMIX ------*/
1006                .put("informix.generatedkey.retrieve","after")
1007                .put("informix.generatedkey.statement","SELECT dbinfo('sqlca.sqlerrd1') FROM systables WHERE tabid=1")
1008                /*-- FIREBIRD ------
1009                 - need to create "sequence" number table first "create generator SEQ_<TABLE>"
1010                 - the 1 is how much to increment the sequence*/
1011                .put("firebird.generatedkey.retrieve","before")
1012                .put("firebird.generatedkey.statement","SELECT GEN_ID(SEQ_<TABLE>, 1) FROM RDB$DATABASE")
1013                /*-- DERBY ------*/
1014                .put("derby.generatedkey.retrieve","after")
1015                .put("derby.generatedkey.statement","VALUES IDENTITY_VAL_LOCAL()")
1016                .build();
1017        
1018        private static final String getMechanismPropertyWithSuffix(String engine,String suffix) {
1019                return checkNotNull(AUTOINC_MECHANISM.get(engine + "." + suffix), "NOT FOUND %s property for %s database",suffix,engine);
1020        }
1021        
1022        private synchronized void autokeyMechanismInit(Connection connection) throws SQLException{              
1023                DatabaseMetaData meta = checkNotNull(connection,"connection is null").getMetaData();
1024                if(meta.supportsGetGeneratedKeys()){
1025                        generatedkeyRetrieve =  "auto";
1026                        generatedkeyStatement = null;
1027                }else{
1028                        String engine = new StringTokenizer(meta.getDatabaseProductName()).nextToken().toLowerCase();
1029                        generatedkeyRetrieve = getMechanismPropertyWithSuffix(engine,RETRIEVE_SUFFIX).toLowerCase();
1030                        generatedkeyStatement = getMechanismPropertyWithSuffix(engine,RETRIEVE_SUFFIX);
1031                }
1032        }
1033        
1034        String getGeneratedkeyRetrieve(Connection c){
1035                // double check
1036                if(generatedkeyRetrieve == null){
1037                        synchronized (this) {
1038                                if(generatedkeyRetrieve == null){
1039                                        try {
1040                                                autokeyMechanismInit(c);
1041                                        } catch (SQLException e) {
1042                                                throw new RuntimeException(e);
1043                                        } 
1044                                }
1045                        }
1046                }
1047                return generatedkeyRetrieve;
1048        }
1049        
1050        AutoKeyRetrieveType getGeneratedkeyRetrieveType(Connection c){
1051                if(retrieveType == null){
1052                        synchronized (this) {
1053                                if(retrieveType == null){
1054                                        retrieveType = AutoKeyRetrieveType.valueOf(getGeneratedkeyRetrieve(c));
1055                                }
1056                        }
1057                }
1058                return retrieveType;
1059        }
1060        String getGeneratedkeyStatement(Connection c){
1061                // double check
1062                if(generatedkeyStatement == null &&  !AutoKeyRetrieveType.auto.equals(retrieveType)){
1063                        synchronized (this) {
1064                                if(generatedkeyStatement == null &&  !AutoKeyRetrieveType.auto.equals(retrieveType)){
1065                                        try {
1066                                                autokeyMechanismInit(c);
1067                                        } catch (SQLException e) {
1068                                                throw new RuntimeException(e);
1069                                        }
1070                                }
1071                        }
1072                }
1073                return generatedkeyStatement;
1074        }
1075
1076        public void setDebug(boolean isDebug) {
1077                this.isDebug = isDebug;
1078        }
1079
1080}