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