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