001/*******************************************************************************
002The MIT License (MIT)
003
004Copyright (c) 2024 KILLCODING.COM
005
006Permission is hereby granted, free of charge, to any person obtaining a copy
007of this software and associated documentation files (the "Software"), to deal
008in the Software without restriction, including without limitation the rights
009to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
010copies of the Software, and to permit persons to whom the Software is
011furnished to do so, subject to the following conditions:
012
013The above copyright notice and this permission notice shall be included in
014all copies or substantial portions of the Software.
015
016THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
017IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
018FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
019AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
020LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
021OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
022THE SOFTWARE.
023*****************************************************************************/
024package com.killcoding.datasource;
025
026import java.sql.Connection;
027import java.sql.PreparedStatement;
028import java.util.List;
029import java.sql.SQLException;
030import java.sql.Types;
031import java.util.Map;
032import java.sql.ResultSet;
033import java.sql.ResultSetMetaData;
034import java.util.HashMap;
035import java.util.ArrayList;
036import java.util.regex.Pattern;
037import java.util.regex.Matcher;
038import java.util.Arrays;
039import com.killcoding.tool.ResultMap;
040import java.sql.DatabaseMetaData;
041import com.killcoding.log.LoggerFactory;
042import com.killcoding.log.Logger;
043import java.util.concurrent.Executors;
044import com.killcoding.datasource.DriverConnection;
045import com.killcoding.datasource.DriverDataSource;
046import java.util.concurrent.ConcurrentHashMap;
047import com.killcoding.tool.CommonTools;
048import com.killcoding.tool.ConfigProperties;
049import java.io.File;
050import java.text.DateFormat;
051import java.text.SimpleDateFormat;
052import com.killcoding.tool.FileTools;
053import java.io.IOException;
054import java.nio.file.Files;
055import java.util.Date;
056import java.nio.file.Paths;
057import java.nio.file.Path;
058import java.nio.file.StandardCopyOption;
059import com.killcoding.cache.CacheArray;
060import com.killcoding.cache.CacheArrayFilter;
061import com.killcoding.tool.CodeEscape;
062import java.sql.Blob;
063import java.util.stream.Collectors;
064import java.net.URI;
065import java.util.Comparator;
066import java.sql.Timestamp;
067import java.sql.Clob;
068import java.io.InputStream;
069
070/**
071 * This class is execute sql base class.
072 * Support database replication.
073 * Support database multi-activity.
074 * Support database CRUD
075 * */
076public class DriverExecutor {
077
078        protected final static Map<Integer, List<DriverExecutor>> SYNC_EXECUTOR_MARK = new ConcurrentHashMap<Integer, List<DriverExecutor>>();
079        private final static Map<Integer, Long> SYNC_CONN_ERROR_TIME = new ConcurrentHashMap<Integer, Long>();
080        
081        private final static Map<String,String> SQL_LOG_MSG_MAPPING = new ConcurrentHashMap<String,String>();
082        private final static Map<String,Boolean> SQL_LOG_OVERSPEND_MAPPING = new ConcurrentHashMap<String,Boolean>();
083
084        protected Logger log = null;
085
086        public final static String COLUMN_NAME_CASE_UPPER = "UPPER";
087        public final static String COLUMN_NAME_CASE_LOWER = "LOWER";
088        public final static String COLUMN_NAME_CASE_ORIGINAL = "ORIGINAL";
089        public static String COLUMN_NAME_CASE_MODE = COLUMN_NAME_CASE_ORIGINAL;
090
091        protected boolean closed = true;
092        protected Connection connection = null;
093        private static CacheArray sqlLogCacheArray = null;
094
095        /**
096         * New a DriverExecutor object
097         * @param connection - JDBC connection
098         * */
099        public DriverExecutor(Connection connection) {
100                super();
101                log = LoggerFactory.getLogger(this.getClass());
102                this.connection = connection;
103                writeSqlLog("open", 0, "open", "");
104                closed = (this.connection == null);
105        }
106
107        /**
108         * Get current Connection
109         * @return Connection
110         * */
111        public Connection getConnection() {
112                return connection;
113        }
114
115        /**
116         * Get column classes by table name
117         * @exception SQLException
118         * @return Map<String,Object> - column and java type class mapping
119         * @param tableName - table name
120         * */
121        public Map<String, Object> getColumnClasses(String tableName) throws SQLException {
122                Map<String, Object> types = null;
123                ResultSet result = null;
124                PreparedStatement statement = null;
125                String sql = String.format("SELECT * FROM %s WHERE 1 = 0", tableName);
126                statement = connection.prepareStatement(sql);
127                result = statement.executeQuery();
128                final ResultSetMetaData rsmd = result.getMetaData();
129                for (int i = 0; i < rsmd.getColumnCount(); i++) {
130                        if (types == null) {
131                                types = new ResultMap<String, Object>();
132                        }
133                        String cn = rsmd.getColumnClassName(i + 1);
134                        types.put(converCase(rsmd.getColumnLabel(i + 1)), cn);
135                }
136                return types;
137        }
138
139        /**
140         * Get column db data types by table name
141         * @exception SQLException
142         * @return Map<String, Object> - column and db data type mapping
143         * @param tableName - Table name
144         * */
145        public Map<String, Object> getColumnTypes(String tableName) throws SQLException {
146                ResultSet result = null;
147                PreparedStatement statement = null;
148                String sql = String.format("SELECT * FROM %s WHERE 1 = 0", tableName);
149                Map<String, Object> types = null;
150                statement = connection.prepareStatement(sql);
151                result = statement.executeQuery();
152                final ResultSetMetaData rsmd = result.getMetaData();
153                for (int i = 0; i < rsmd.getColumnCount(); i++) {
154                        if (types == null) {
155                                types = new ResultMap<String, Object>();
156                        }
157                        types.put(converCase(rsmd.getColumnLabel(i + 1)), rsmd.getColumnTypeName(i + 1));
158                }
159                return types;
160        }
161
162        /**
163         * Show column data type and java type mapping
164         * @exception SQLException
165         * @return List<Map<String, Object>> - Mapping list
166         * @param tableName - Table name
167         * */
168        public List<Map<String, Object>> desc(String tableName) throws SQLException {
169                List<String> primaryKeys = getPrimaryKeys(tableName);
170                List<Map<String, Object>> results = new ArrayList<Map<String, Object>>();
171                ResultSet result = null;
172                PreparedStatement statement = null;
173                String sql = String.format("SELECT * FROM %s WHERE 1 = 0", tableName);
174                statement = connection.prepareStatement(sql);
175                result = statement.executeQuery();
176                final ResultSetMetaData rsmd = result.getMetaData();
177                for (int i = 0; i < rsmd.getColumnCount(); i++) {
178                        Map<String, Object> types = new ResultMap<String, Object>();
179                        int ci = i + 1;
180                        boolean nullable = rsmd.isNullable(ci) == 1;
181                        String name = converCase(rsmd.getColumnLabel(ci));
182                        String isPk = "UNKNOWN";
183                        if (primaryKeys != null) {
184                                isPk = primaryKeys.contains(name) ? "Y" : "N";
185                        }
186                        types.put("NAME", name);
187                        types.put("PRIMARY_KEY", isPk);
188                        types.put("DATA_TYPE", rsmd.getColumnTypeName(ci));
189                        types.put("JAVA_TYPE", rsmd.getColumnClassName(ci));
190                        types.put("PRECISION", rsmd.getPrecision(ci));
191                        types.put("ALLOW_NULLABLE", nullable ? "Y" : 'N');
192                        results.add(types);
193                }
194                return results;
195        }
196
197        /**
198         * Get primary Keys by table name
199         * @return List<String> - Primary Keys
200         * @param _tableName - Table name
201         * */
202        public List<String> getPrimaryKeys(String _tableName) {
203                try {
204                        List<String> pks = new ArrayList<String>();
205                        DatabaseMetaData meta = connection.getMetaData();
206                        ResultSet tables = meta.getTables(null, null, "%", new String[] { "TABLE" });
207                        while (tables.next()) {
208                                String catalog = tables.getString("TABLE_CAT");
209                                String schema = tables.getString("TABLE_SCHEM");
210                                String tableName = tables.getString("TABLE_NAME");
211                                if (tableName.equalsIgnoreCase(_tableName)) {
212                                        ResultSet primaryKeys = meta.getPrimaryKeys(catalog, schema, tableName);
213                                        while (primaryKeys.next()) {
214                                                pks.add(primaryKeys.getString("COLUMN_NAME"));
215                                        }
216                                        break;
217                                }
218                        }
219                        return pks;
220                } catch (Exception e) {
221                        log.warn(e);
222                        return null;
223                }
224        }
225
226        /**
227         * Get all tables
228         * @return List<Map<String, Object>>
229         * @exception SQLException
230         * */
231        public List<Map<String, Object>> getAllTables() throws SQLException {
232                List<Map<String, Object>> tablesList = new ArrayList<Map<String, Object>>();
233                DatabaseMetaData meta = connection.getMetaData();
234                ResultSet tables = meta.getTables(null, null, "%", new String[] { "TABLE" });
235                while (tables.next()) {
236                        String catalog = tables.getString("TABLE_CAT");
237                        String schema = tables.getString("TABLE_SCHEM");
238                        String tableName = tables.getString("TABLE_NAME");
239                        Map<String, Object> t = new ResultMap<String, Object>();
240                        t.put("TABLE_SCHEMA", schema);
241                        t.put("TABLE_NAME", tableName);
242                        tablesList.add(t);
243                }
244                return tablesList;
245        }
246
247        /**
248         * Get all tables by schema
249         * @return List<Map<String, Object>>
250         * @exception SQLException
251         * */
252        public List<Map<String, Object>> getAllTables(String _schema) throws SQLException {
253                List<Map<String, Object>> tablesList = new ArrayList<Map<String, Object>>();
254                DatabaseMetaData meta = connection.getMetaData();
255                ResultSet tables = meta.getTables(null, null, "%", new String[] { "TABLE" });
256                while (tables.next()) {
257                        String schema = tables.getString("TABLE_SCHEM");
258                        if (schema != null && schema.equalsIgnoreCase(_schema)) {
259                                String catalog = tables.getString("TABLE_CAT");
260                                String tableName = tables.getString("TABLE_NAME");
261                                Map<String, Object> t = new ResultMap<String, Object>();
262                                t.put("TABLE_SCHEMA", schema);
263                                t.put("TABLE_NAME", tableName);
264                                tablesList.add(t);
265                        }
266                }
267                return tablesList;
268        }
269
270        /**
271         * Query first record
272         * @exception SQLException
273         * @return Map<String,Object> - First result
274         * @param sql - Condition use format ':column_name'
275         * @param params
276         * */
277        public Map<String, Object> first(String sql, Map<String, Object> params) throws SQLException {
278                List<Map<String, Object>> list = find(0, 1, sql, params);
279                if (list.size() > 0) {
280                        return list.get(0);
281                }
282                return null;
283        }
284
285        /**
286         * Query first record
287         * @exception SQLException
288         * @return Map<String,Object> - First result
289         * @param sql
290         * */
291        public Map<String, Object> first(String sql) throws SQLException {
292                return first(sql, Arrays.asList(new Object[] {}));
293        }
294
295        /**
296         * Query first record
297         * @exception SQLException
298         * @return Map<String,Object> - First result
299         * @param sql - Condition use format '?'
300         * @param params
301         * */
302        public Map<String, Object> first(String sql, List<Object> params) throws SQLException {
303                List<Map<String, Object>> list = find(0, 1, sql, params);
304                if (list.size() > 0) {
305                        return list.get(0);
306                }
307                return null;
308        }
309
310        /**
311         * Query all matched records
312         * @exception SQLException
313         * @return List<Map<String, Object>>
314         * @param sql 
315         * @param params
316         * */
317        public List<Map<String, Object>> find(String sql) throws SQLException {
318                return find(0, 0, sql, Arrays.asList(new Object[] {}));
319        }
320
321        /**
322         * Query all matched records
323         * @exception SQLException
324         * @return List<Map<String, Object>>
325         * @param cursorStart - JDBC result Cursor start index
326         * @param maxRows - JDBC result max rows (JDBC limited rows 50,000,000)
327         * @param sql 
328         * @param params
329         * */
330        public List<Map<String, Object>> find(int cursorStart, int maxRows, String sql) throws SQLException {
331                return find(cursorStart, maxRows, sql, Arrays.asList(new Object[] {}));
332        }
333
334        /**
335         * Query all matched records
336         * @exception SQLException
337         * @return List<Map<String, Object>>
338         * @param sql - Condition use format ':column_name'
339         * @param params
340         * */
341        public List<Map<String, Object>> find(String sql, Map<String, Object> params) throws SQLException {
342                return find(0, 0, sql, params);
343        }
344
345        /**
346         * Query all matched records
347         * @exception SQLException
348         * @return List<Map<String, Object>>
349         * @param cursorStart - JDBC result Cursor start index
350         * @param maxRows - JDBC result max rows (JDBC limited rows 50,000,000)
351         * @param sql - Condition use format ':column_name'
352         * @param params
353         * */
354        public List<Map<String, Object>> find(int cursorStart, int maxRows, String sql, Map<String, Object> params)
355                        throws SQLException {
356                String csql = converSql(sql);
357                List<Object> cparams = converParams(sql, params);
358                return find(cursorStart, maxRows, csql, cparams);
359        }
360
361        /**
362         * Query all matched records
363         * @exception SQLException
364         * @return List<Map<String, Object>>
365         * @param cursorStart - JDBC result Cursor start index
366         * @param maxRows - JDBC result max rows (JDBC limited rows 50,000,000)
367         * @param sql - Condition use format '?'
368         * @param params
369         * */
370        public List<Map<String, Object>> find(int cursorStart, int maxRows, String sql, List<Object> params)
371                        throws SQLException {
372                long begin = System.currentTimeMillis();
373                boolean allowedLog = writeSqlLog("find", begin,
374                                String.format("%s [cursorStart=%s,maxRows=%s]", sql, cursorStart, maxRows), params);
375
376                PreparedStatement statement = null;
377                Map<String, Object> row = null;
378                ResultSet result = null;
379                final List<Map<String, Object>> rows = new ArrayList<Map<String, Object>>();
380                try {
381                        // ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE
382                        // ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY
383                        statement = connection.prepareStatement(sql, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
384                        if (params != null) {
385                                int size = params.size();
386                                for (int i = 0; i < size; i++) {
387                                        int ci = i + 1;
388                                        Object param = params.get(i);
389                                        if (param == null) {
390                                                statement.setNull(ci, Types.VARCHAR);
391                                        } else {
392                                                statement.setObject(ci, param);
393                                        }
394                                }
395                        }
396                        if (maxRows > 0) {
397                                statement.setMaxRows(maxRows);
398                        }
399                        result = statement.executeQuery();
400                        result.absolute(cursorStart);
401                        final ResultSetMetaData rsmd = result.getMetaData();
402                        final int c = rsmd.getColumnCount();
403                        while (result.next()) {
404                                row = new ResultMap<String, Object>();
405                                for (int i = 0; i < c; i++) {
406                                        int ci = i + 1;
407                                        Object value = null;
408                                        Object originValue = result.getObject(ci);
409                                        if (originValue == null) {
410                                                value = originValue;
411                                        } else if (originValue instanceof Blob) {
412                                                Blob blobValue = (Blob) originValue;
413                                                InputStream is = null;
414                                                try {
415                                                        is = blobValue.getBinaryStream();
416                                                        if(is != null) value = is.readAllBytes();
417                                                } catch (IOException e) {
418                                                        throw new SQLException(e.getMessage(), e);
419                                                } finally {
420                                                        if (blobValue != null) {
421                                                                try {
422                                                                        blobValue.free();
423                                                                } catch (SQLException e) {
424                                                                        throw e;
425                                                                }
426                                                        }
427                                                        if (is != null) {
428                                                                try {
429                                                                        is.close();
430                                                                } catch (IOException e) {
431                                                                        throw new SQLException(e.getMessage(), e);
432                                                                }
433                                                        }
434                                                }
435                                        } else if (originValue instanceof Clob) {
436                                                Clob clobValue = (Clob) originValue;
437                                                InputStream is = null;
438                                                try {
439                                                        is = clobValue.getAsciiStream();
440                                                        if(is != null) value = is.readAllBytes();
441                                                } catch (IOException e) {
442                                                        throw new SQLException(e.getMessage(), e);
443                                                } finally {
444                                                        if (clobValue != null) {
445                                                                try {
446                                                                        clobValue.free();
447                                                                } catch (SQLException e) {
448                                                                        throw e;
449                                                                }
450                                                        }
451                                                        if (is != null) {
452                                                                try {
453                                                                        is.close();
454                                                                } catch (IOException e) {
455                                                                        throw new SQLException(e.getMessage(), e);
456                                                                }
457                                                        }
458                                                }
459                                        } else {
460                                                value = originValue;
461                                        }
462                                        row.put(converCase(rsmd.getColumnLabel(ci)), value);
463                                }
464                                rows.add(row);
465                        }
466                        if (allowedLog) {
467                                writeSqlLog("rows", begin, "rows", rows.size());
468                                long spend = System.currentTimeMillis() - begin;
469                                writeSqlLog("spend", begin, "spend", spend);
470                        }
471                        return rows;
472                } catch (SQLException e) {
473                        if (allowedLog)
474                                writeSqlLog("error", begin, "error", String.format("%s,%s", e.getErrorCode(), e.getMessage()));
475
476                        throw e;
477                } finally {
478                        if (result != null)
479                                result.close();
480
481                        if (statement != null)
482                                statement.close();
483                }
484        }
485
486        /**
487         * Execute stored proc (and return boolean) 
488         * @param sql - Query sql 
489         * @exception SQLException 
490         * @return boolean
491         * */
492        public boolean callAndReturnBoolean(String sql) throws SQLException {
493                return callAndReturnBoolean(sql, Arrays.asList(new Object[] {}));
494        }
495
496        /**
497         * Execute stored proc (and return boolean) 
498         * @param sql - Query sql, condition use format ':column_name'
499         * @param params
500         * @exception SQLException 
501         * @return boolean
502         * */
503        public boolean callAndReturnBoolean(String sql, Map<String, Object> params) throws SQLException {
504                String csql = converSql(sql);
505                List<Object> cparams = converParams(sql, params);
506                return callAndReturnBoolean(sql, params);
507        }
508
509        /**
510         * Execute stored proc (and return boolean) 
511         * @param sql - Query sql,condition use format '?'
512         * @param params
513         * @exception SQLException 
514         * @return boolean
515         * */
516        public boolean callAndReturnBoolean(String sql, List<Object> params) throws SQLException {
517
518                if (!checkSqlAvailable(sql))
519                        return false;
520
521                long begin = System.currentTimeMillis();
522                boolean allowedLog = writeSqlLog("call", begin, sql, params);
523
524                PreparedStatement statement = null;
525                try {
526                        statement = connection.prepareCall("{" + sql + "}");
527                        if (params != null) {
528                                int size = params.size();
529                                for (int i = 0; i < size; i++) {
530                                        Object param = params.get(i);
531                                        if (param == null) {
532                                                statement.setNull(i + 1, Types.VARCHAR);
533                                        } else {
534                                                statement.setObject(i + 1, param);
535                                        }
536                                }
537                        }
538
539                        boolean returnResult = statement.execute();
540
541                        if (allowedLog) {
542                                writeSqlLog("return", begin, "return", returnResult);
543                                long spend = System.currentTimeMillis() - begin;
544                                writeSqlLog("spend", begin, "spend", spend);
545                        }
546
547                        callAndReturnBooleanSync(connection, begin, sql, params, returnResult);
548
549                        return returnResult;
550                } catch (SQLException e) {
551                        if (allowedLog)
552                                writeSqlLog("error", begin, "error", String.format("%s,%s", e.getErrorCode(), e.getMessage()));
553
554                        throw e;
555                } finally {
556                        if (statement != null)
557                                statement.close();
558                }
559        }
560
561        /**
562         * Execute stored proc (and return rows) 
563         * @param sql - Query sql
564         * @exception SQLException 
565         * @return int
566         * */
567        public int callAndReturnRows(String sql) throws SQLException {
568                return callAndReturnRows(sql, Arrays.asList(new Object[] {}));
569        }
570
571        /**
572         * Execute stored proc (and return rows) 
573         * @param sql - Query sql,condition use format ':column_name'
574         * @param params
575         * @exception SQLException 
576         * @return int
577         * */
578        public int callAndReturnRows(String sql, Map<String, Object> params) throws SQLException {
579                String csql = converSql(sql);
580                List<Object> cparams = converParams(sql, params);
581                return callAndReturnRows(sql, params);
582        }
583
584        /**
585         * Execute stored proc (and return rows) 
586         * @param sql - Query sql,condition use format '?'
587         * @param params
588         * @exception SQLException 
589         * @return int
590         * */
591        public int callAndReturnRows(String sql, List<Object> params) throws SQLException {
592
593                if (!checkSqlAvailable(sql))
594                        return -1;
595
596                long begin = System.currentTimeMillis();
597                boolean allowedLog = writeSqlLog("call", begin, sql, params);
598
599                PreparedStatement statement = null;
600                try {
601                        statement = connection.prepareCall("{" + sql + "}");
602                        if (params != null) {
603                                int size = params.size();
604                                for (int i = 0; i < size; i++) {
605                                        Object param = params.get(i);
606                                        if (param == null) {
607                                                statement.setNull(i + 1, Types.VARCHAR);
608                                        } else {
609                                                statement.setObject(i + 1, param);
610                                        }
611                                }
612                        }
613                        int row = statement.executeUpdate();
614
615                        if (allowedLog) {
616                                writeSqlLog("return", begin, "return", row);
617                                long spend = System.currentTimeMillis() - begin;
618                                writeSqlLog("spend", begin, "spend", spend);
619                        }
620
621                        callAndReturnRowsSync(connection, begin, sql, params, row);
622
623                        return row;
624                } catch (SQLException e) {
625                        if (allowedLog)
626                                writeSqlLog("error", begin, "error", String.format("%s,%s", e.getErrorCode(), e.getMessage()));
627
628                        throw e;
629                } finally {
630                        if (statement != null)
631                                statement.close();
632                }
633        }
634
635        /**
636         * Execute stored proc (and return List) 
637         * @param sql - Query sql
638         * @exception SQLException 
639         * @return List<Map<String, Object>>
640         * */
641        public List<Map<String, Object>> callAndReturnList(String sql) throws SQLException {
642                return callAndReturnList(0, 0, sql, Arrays.asList(new Object[] {}));
643        }
644
645        /**
646         * Execute stored proc (and return List) 
647         * @param cursorStart - JDBC result Cursor start index
648         * @param maxRows - JDBC result max rows (JDBC limited rows 50,000,000) 
649         * @param sql - Query sql
650         * @exception SQLException 
651         * @return List<Map<String, Object>>
652         * */
653        public List<Map<String, Object>> callAndReturnList(int cursorStart, int maxRows, String sql) throws SQLException {
654                return callAndReturnList(cursorStart, maxRows, sql, Arrays.asList(new Object[] {}));
655        }
656
657        /**
658         * Execute stored proc (and return List) 
659         * @param cursorStart - JDBC result Cursor start index
660         * @param maxRows - JDBC result max rows (JDBC limited rows 50,000,000) 
661         * @param sql - Query sql, condition use format ':column_name'
662         * @exception SQLException 
663         * @return List<Map<String, Object>>
664         * */
665        public List<Map<String, Object>> callAndReturnList(int cursorStart, int maxRows, String sql,
666                        Map<String, Object> params) throws SQLException {
667                String csql = converSql(sql);
668                List<Object> cparams = converParams(sql, params);
669                return callAndReturnList(cursorStart, maxRows, sql, params);
670        }
671
672        /**
673         * Execute stored proc (and return List) 
674         * @param cursorStart - JDBC result Cursor start index
675         * @param maxRows - JDBC result max rows (JDBC limited rows 50,000,000) 
676         * @param sql - Query sql, condition use format '?'
677         * @exception SQLException 
678         * @return List<Map<String, Object>>
679         * */
680        public List<Map<String, Object>> callAndReturnList(int cursorStart, int maxRows, String sql, List<Object> params)
681                        throws SQLException {
682
683                if (!checkSqlAvailable(sql))
684                        return null;
685
686                long begin = System.currentTimeMillis();
687                boolean allowedLog = writeSqlLog("call", begin,
688                                String.format("%s [cursorStart=%s,maxRows=%s]", sql, cursorStart, maxRows), params);
689
690                PreparedStatement statement = null;
691                Map<String, Object> row = null;
692                ResultSet result = null;
693                final List<Map<String, Object>> rows = new ArrayList<Map<String, Object>>();
694                try {
695                        statement = connection.prepareCall("{" + sql + "}");
696                        if (params != null) {
697                                int size = params.size();
698                                for (int i = 0; i < size; i++) {
699                                        int ci = i + 1;
700                                        Object param = params.get(i);
701                                        if (param == null) {
702                                                statement.setNull(ci, Types.VARCHAR);
703                                        } else {
704                                                statement.setObject(ci, param);
705                                        }
706                                }
707                        }
708                        if (maxRows > 0) {
709                                statement.setMaxRows(maxRows);
710                        }
711                        result = statement.executeQuery();
712                        final ResultSetMetaData rsmd = result.getMetaData();
713                        final int c = rsmd.getColumnCount();
714                        int rowIndex = 0;
715                        while (result.next()) {
716                                if (rowIndex >= cursorStart) {
717                                        row = new ResultMap<String, Object>();
718                                        for (int i = 0; i < c; i++) {
719                                                int ci = i + 1;
720                                                Object value = null;
721                                                Object originValue = result.getObject(ci);
722                                                if (originValue == null) {
723                                                        value = originValue;
724                                                } else if (originValue instanceof Blob) {
725                                                        Blob blobValue = (Blob) originValue;
726                                                        InputStream is = null;
727                                                        try {
728                                                                is = blobValue.getBinaryStream();
729                                                                if(is != null) value = is.readAllBytes();
730                                                        } catch (IOException e) {
731                                                                throw new SQLException(e.getMessage(), e);
732                                                        } finally {
733                                                                if (blobValue != null) {
734                                                                        try {
735                                                                                blobValue.free();
736                                                                        } catch (SQLException e) {
737                                                                                throw e;
738                                                                        }
739                                                                }
740                                                                if (is != null) {
741                                                                        try {
742                                                                                is.close();
743                                                                        } catch (IOException e) {
744                                                                                throw new SQLException(e.getMessage(), e);
745                                                                        }
746                                                                }
747                                                        }
748                                                } else if (originValue instanceof Clob) {
749                                                        Clob clobValue = (Clob) originValue;
750                                                        InputStream is = null;
751                                                        try {
752                                                                is = clobValue.getAsciiStream();
753                                                                if(is != null) value = is.readAllBytes();
754                                                        } catch (IOException e) {
755                                                                throw new SQLException(e.getMessage(), e);
756                                                        } finally {
757                                                                if (clobValue != null) {
758                                                                        try {
759                                                                                clobValue.free();
760                                                                        } catch (SQLException e) {
761                                                                                throw e;
762                                                                        }
763                                                                }
764                                                                if (is != null) {
765                                                                        try {
766                                                                                is.close();
767                                                                        } catch (IOException e) {
768                                                                                throw new SQLException(e.getMessage(), e);
769                                                                        }
770                                                                }
771                                                        }
772                                                } else {
773                                                        value = originValue;
774                                                }
775                                                row.put(converCase(rsmd.getColumnLabel(ci)), value);
776                                        }
777                                        rows.add(row);
778                                }
779                                rowIndex++;
780                        }
781
782                        if (allowedLog) {
783                                writeSqlLog("rows", begin, "rows", rows);
784                                long spend = System.currentTimeMillis() - begin;
785                                writeSqlLog("spend", begin, "spend", spend);
786                        }
787
788                        callAndReturnListSync(connection, cursorStart, maxRows, sql, params);
789
790                        return rows;
791                } catch (SQLException e) {
792                        if (allowedLog)
793                                writeSqlLog("error", begin, "error", String.format("%s,%s", e.getErrorCode(), e.getMessage()));
794                        throw e;
795                } finally {
796                        if (result != null)
797                                result.close();
798
799                        if (statement != null)
800                                statement.close();
801                }
802        }
803
804        /**
805         * Execute sql
806         * @exception SQLException
807         * @param sql
808         * @return int
809         * */
810        public int execute(String sql) throws SQLException {
811                return execute(sql, Arrays.asList(new Object[] {}));
812        }
813
814        /**
815         * Execute sql
816         * @exception SQLException
817         * @param sql - Condition use format ':column_name'
818         * @param params 
819         * @return int
820         * */
821        public int execute(String sql, Map<String, Object> params) throws SQLException {
822                String csql = converSql(sql);
823                List<Object> cparams = converParams(sql, params);
824                return execute(csql, cparams);
825        }
826
827        /**
828         * Execute sql
829         * @exception SQLException
830         * @param sql - Condition use format '?'
831         * @param params 
832         * @return int
833         * */
834        public int execute(String sql, List<Object> params) throws SQLException {
835                if (!checkSqlAvailable(sql))
836                        return -1;
837
838                long begin = System.currentTimeMillis();
839                boolean allowedLog = writeSqlLog("execute", begin, sql, params);
840
841                PreparedStatement statement = null;
842                try {
843                        statement = connection.prepareStatement(sql);
844                        if (params != null) {
845                                int size = params.size();
846                                for (int i = 0; i < size; i++) {
847                                        Object param = params.get(i);
848                                        if (param == null) {
849                                                statement.setNull(i + 1, Types.VARCHAR);
850                                        } else {
851                                                statement.setObject(i + 1, param);
852                                        }
853                                }
854                        }
855                        int row = statement.executeUpdate();
856
857                        if (allowedLog) {
858                                writeSqlLog("return", begin, "return", row);
859                                long spend = System.currentTimeMillis() - begin;
860                                writeSqlLog("spend", begin, "spend", spend);
861                        }
862
863                        executeSync(connection, begin, sql, params, row);
864
865                        return row;
866                } catch (SQLException e) {
867                        if (allowedLog)
868                                writeSqlLog("error", begin, "error", String.format("%s,%s", e.getErrorCode(), e.getMessage()));
869                        throw e;
870                } finally {
871                        if (statement != null)
872                                statement.close();
873                }
874        }
875
876        /**
877         * Execute batch
878         * @exception SQLException
879         * @param sql - Condition use format ':column_name'
880         * @param records
881         * @return int - Return rows
882         * */
883        public int executeBatch(String sql, List<Map<String, Object>> records) throws SQLException {
884                String csql = converSql(sql);
885                List<List<Object>> crecords = new ArrayList<List<Object>>();
886                for (Map<String, Object> record : records) {
887                        List<Object> crecord = converParams(sql, record);
888                        crecords.add(crecord);
889                }
890                return executeBatchList(csql, crecords);
891        }
892
893        /**
894         * Execute batch
895         * @exception SQLException
896         * @param sql - Condition use format '?'
897         * @param records
898         * @return int - Return rows
899         * */
900        public int executeBatchList(String sql, List<List<Object>> records) throws SQLException {
901                boolean allowedLog = false;
902                if (!checkSqlAvailable(sql))
903                        return -1;
904
905                long begin = System.currentTimeMillis();
906                if (records != null) {
907                        allowedLog = writeSqlLog("batch", begin, sql, String.format("[batchSize=%s]", records.size()));
908                }
909
910                boolean first = true;
911                PreparedStatement statement = null;
912                try {
913                        for (List<Object> params : records) {
914                                if (first) {
915                                        statement = connection.prepareStatement(sql);
916                                        first = false;
917                                }
918
919                                int size = params.size();
920                                for (int i = 0; i < size; i++) {
921                                        Object param = params.get(i);
922                                        if (param == null) {
923                                                statement.setNull(i + 1, Types.VARCHAR);
924                                        } else {
925                                                statement.setObject(i + 1, param);
926                                        }
927                                }
928
929                                statement.addBatch();
930                        }
931
932                        int sumRow = 0;
933                        if (records.size() > 0) {
934                                int[] rows = statement.executeBatch();
935
936                                for (int r : rows) {
937                                        sumRow += r;
938                                }
939
940                                if (allowedLog) {
941                                        long spend = System.currentTimeMillis() - begin;
942                                        writeSqlLog("return", begin, "return", sumRow);
943                                        writeSqlLog("spend", begin, "spend", spend);
944                                }
945
946                                executeBatchListSync(connection, begin, sql, records, sumRow);
947                        }
948
949                        return sumRow;
950                } catch (SQLException e) {
951                        if (allowedLog)
952                                writeSqlLog("error", begin, "error", String.format("%s,%s", e.getErrorCode(), e.getMessage()));
953
954                        throw e;
955                } finally {
956                        if (statement != null)
957                                statement.close();
958                }
959        }
960
961        /**
962         * Check connection is closed
963         * @return boolean
964         * */
965        public boolean isClosed() {
966                try {
967                    if(closed) return closed;
968                    
969                        return (connection == null || connection.isClosed());
970                } catch (Exception e) {
971                        log.warn(e);
972                        return true;
973                }
974        }
975
976        /**
977         * Abort connection
978         * @exception SQLException
979         * */
980        public void abort() throws SQLException {
981                writeSqlLog("aborting", 0, "aborting", "");
982                if (connection != null) {
983                        try {
984                                if (connection instanceof DriverConnection) {
985                                        abortSyncConnection(connection);
986                                        connection.abort(null);
987                                } else {
988                                        connection.abort(Executors.newFixedThreadPool(1));
989                                }
990                                closed = true;
991                        } catch (SQLException e) {
992                                writeSqlLog("error", 0, "error", String.format("%s,%s", e.getErrorCode(), e.getMessage()));
993                                throw e;
994                        }
995                }
996                writeSqlLog("aborted", 0, "aborted", "");
997        }
998
999        /**
1000         * Close connection
1001         * @exception SQLException
1002         * */
1003        public void close() throws SQLException {
1004                writeSqlLog("closing", 0, "closing", "");
1005                if (connection != null) {
1006                        try {
1007                                closeSyncConnection(connection);
1008                                connection.close();
1009                                closed = true;
1010                        } catch (SQLException e) {
1011                                writeSqlLog("error", 0, "error", String.format("%s,%s", e.getErrorCode(), e.getMessage()));
1012                                throw e;
1013                        }
1014                }
1015                writeSqlLog("closed", 0, "closed", "");
1016        }
1017
1018        /**
1019         * Commit connection
1020         * @exception SQLException
1021         * */
1022        public void commit() throws SQLException {
1023                writeSqlLog("committing", 0, "committing", "");
1024                if (connection != null) {
1025                        try {
1026                                commitSyncConnection(connection);
1027                                connection.commit();
1028                        } catch (SQLException e) {
1029                                writeSqlLog("error", 0, "", String.format("%s,%s", e.getErrorCode(), e.getMessage()));
1030                                throw e;
1031                        }
1032                }
1033                writeSqlLog("committed", 0, "committed", "");
1034        }
1035
1036        /**
1037         * Rollback connection
1038         * @exception SQLException
1039         * */
1040        public void rollback() throws SQLException {
1041                writeSqlLog("rollbacking", 0, "rollbacking", "");
1042                if (connection != null) {
1043                        try {
1044                                rollbackSyncConnection(connection);
1045                                connection.rollback();
1046                        } catch (SQLException e) {
1047                                writeSqlLog("error", 0, "error", String.format("%s,%s", e.getErrorCode(), e.getMessage()));
1048                                throw e;
1049                        }
1050                }
1051                writeSqlLog("rollbacked", 0, "rollbacked", "");
1052        }
1053
1054        /**
1055         * Get origin connection hash code
1056         * @return Integer - hash code
1057         * */
1058        private Integer getOriginConnectionHashCode() {
1059                if (connection instanceof DriverConnection) {
1060                        return ((DriverConnection) connection).getOriginConnectionHashCode();
1061                } else {
1062                        return connection.hashCode();
1063                }
1064        }
1065
1066        /**
1067         * Get data source thread name
1068         * @return String - DataSource thread name
1069         * */
1070        private String getDataSourceName() {
1071                if (connection instanceof DriverConnection) {
1072                        return ((DriverConnection) connection).getDriverDataSource().getName();
1073                } else {
1074                        return null;
1075                }
1076        }
1077
1078        /**
1079         * Conver sql from ':column_name' to '?'
1080         * @return String
1081         * */
1082        protected String converSql(String sql) {
1083                return sql.replaceAll(":[a-zA-Z0-9_]+", "?");
1084        }
1085
1086        /**
1087         * Conver param from map to list
1088         * @param sql
1089         * @param map
1090         * @return List<Object>
1091         * */
1092        protected List<Object> converParams(String sql, Map<String, Object> map) {
1093                List<String> paramKeys = new ArrayList<String>();
1094                Pattern pattern = Pattern.compile(":[a-zA-Z0-9_]+");
1095                Matcher matcher = pattern.matcher(sql);
1096                while (matcher.find()) {
1097                        String key = matcher.group().replaceFirst(":", "");
1098                        paramKeys.add(key);
1099                }
1100
1101                List<Object> params = new ArrayList<Object>();
1102                for (String pk : paramKeys) {
1103                        Object pv = map.get(pk);
1104                        if (pv == null) {
1105                                pv = map.get(pk.toLowerCase());
1106                        }
1107                        if (pv == null) {
1108                                pv = map.get(pk.toUpperCase());
1109                        }
1110                        if (pv instanceof java.util.Date) {
1111                                java.util.Date utilDate = (java.util.Date) pv;
1112                                java.sql.Timestamp sqlDate = new java.sql.Timestamp(utilDate.getTime());
1113                                params.add(sqlDate);
1114                        } else {
1115                                params.add(pv);
1116                        }
1117                }
1118
1119                return params;
1120        }
1121
1122        /**
1123         * Covner to upper or lower
1124         * @param s - Column name or table name
1125         * @return String
1126         * */
1127        protected String converCase(String s) {
1128                if (COLUMN_NAME_CASE_MODE.equals(COLUMN_NAME_CASE_UPPER)) {
1129                        return s.toUpperCase();
1130                } else if (COLUMN_NAME_CASE_MODE.equals(COLUMN_NAME_CASE_LOWER)) {
1131                        return s.toLowerCase();
1132                } else {
1133                        return s;
1134                }
1135        }
1136
1137        /**
1138         * Check available sql to write to log
1139         * @return boolean
1140         * @param connection - Connection
1141         * @param sql
1142         * */
1143        protected static boolean checkSqlLogAvailable(Connection connection, String sql) {
1144                if (connection instanceof DriverConnection) {
1145                        DriverConnection dc = (DriverConnection) connection;
1146                        DriverDataSource dds = dc.getDriverDataSource();
1147                        ConfigProperties configProperties = dds.getConfigProperties();
1148                        List<String> sqlAllowed = configProperties.getArray("SqlLogAllowed");
1149                        List<String> sqlIgnored = configProperties.getArray("SqlLogIgnored");
1150                        return checkSqlAvailable(sql, sqlAllowed, sqlIgnored);
1151                }
1152                return false;
1153        }
1154
1155        /**
1156         * Check available sql to execute
1157         * @return boolean
1158         * @param sql
1159         * */
1160        protected boolean checkSqlAvailable(String sql) {
1161                return checkSqlAvailable(connection, sql);
1162        }
1163
1164        /**
1165         * Check available sql to execute
1166         * @return boolean
1167         * @param connection
1168         * @param sql
1169         * */
1170        protected static boolean checkSqlAvailable(Connection connection, String sql) {
1171                if (connection instanceof DriverConnection) {
1172                        DriverConnection dc = (DriverConnection) connection;
1173                        DriverDataSource dds = dc.getDriverDataSource();
1174                        ConfigProperties configProperties = dds.getConfigProperties();
1175                        List<String> sqlAllowed = configProperties.getArray("SqlExecuteAllowed");
1176                        List<String> sqlIgnored = configProperties.getArray("SqlExecuteIgnored");
1177                        return checkSqlAvailable(sql, sqlAllowed, sqlIgnored);
1178                }
1179                return true;
1180        }
1181
1182        /**
1183         * Check available sql to execute
1184         * @return boolean
1185         * @param sql
1186         * @param sqlAllowed - From DataSources.properties
1187         * @param sqlIgnored - From DataSources.properties
1188         * */
1189        private static boolean checkSqlAvailable(String sql, List<String> sqlAllowed, List<String> sqlIgnored) {
1190                boolean matchedAllowed = true;
1191                boolean matchedIgnored = false;
1192                if (sqlAllowed != null) {
1193                        for (String regex : sqlAllowed) {
1194                                if (!CommonTools.isBlank(regex)) {
1195                                        matchedAllowed = sql.matches(regex);
1196
1197                                        if (matchedAllowed)
1198                                                break;
1199                                }
1200                        }
1201                }
1202                if (matchedAllowed && sqlIgnored != null) {
1203                        for (String regex : sqlIgnored) {
1204                                if (!CommonTools.isBlank(regex)) {
1205                                        matchedIgnored = sql.matches(regex);
1206
1207                                        if (matchedIgnored)
1208                                                break;
1209                                }
1210                        }
1211                }
1212
1213                boolean b = matchedAllowed && !matchedIgnored;
1214                if (!b) {
1215                        LoggerFactory.getLogger(DriverExecutor.class).debug("Not available - '{}'", sql);
1216                }
1217                return b;
1218        }
1219
1220        /**
1221         * Write sql log
1222         * @return boolean 
1223         * @param type
1224         * @param seq
1225         * @param sql
1226         * @param params
1227         * */
1228        protected synchronized boolean writeSqlLog(String type, long seq, String sql, Object params) {
1229                return writeSqlLog(this.hashCode(),connection, type, seq, sql, params);
1230        }
1231
1232        /**
1233         * Write sql log
1234         * @return boolean 
1235         * @param connection
1236         * @param type
1237         * @param seq
1238         * @param sql
1239         * @param params
1240         * */
1241        protected synchronized static boolean writeSqlLog(int deHashCode,Connection connection, String type, long seq, String sql,
1242                        Object params) {
1243                if (connection instanceof DriverConnection) {
1244                        if (!checkSqlLogAvailable(connection, sql))
1245                                return false;
1246
1247                        final String header = String.format(
1248                                        "LOG_DATE,LOG_HOUR,LOG_MI,LOG_SEC,LOG_MS,LOG_HOST,LOG_THREAD,LOG_DS,LOG_CONN_ID,LOG_EXEC_ID,LOG_TYPE,LOG_SEQ,LOG_SQL,LOG_PARAMS%s",
1249                                        System.lineSeparator());
1250                        final DateFormat df = new SimpleDateFormat("yyyyMMdd");
1251                        final DateFormat dtf = new SimpleDateFormat("yyyyMMdd,HH,mm,ss,SSS");
1252                        DriverConnection dc = (DriverConnection) connection;
1253                        Integer connHashCode = dc.getOriginConnectionHashCode();
1254                        DriverDataSource dds = dc.getDriverDataSource();
1255                        ConfigProperties configProperties = dds.getConfigProperties();
1256
1257                        boolean logEnable = configProperties.getBoolean("SqlLogEnable", false);
1258                        long overspend = configProperties.getMilliSeconds("SqlLogOverspend",0L);
1259
1260                        if (!logEnable)
1261                                return false;
1262
1263                        final String defaultLogFolderPath = String.format("%s/SqlLog/",
1264                                        CommonTools.getJarPath(DriverExecutor.class));
1265                        final String logFolderPath = configProperties.getString("SqlLogFolder", defaultLogFolderPath);
1266                        final long maxFileSize = configProperties.getFileSize("SqlLogMaxFileSize", 1024 * 1024 * 10L);
1267                        final int archiveDays = configProperties.getInteger("SqlLogArchiveDays", 31);
1268                        final int logParamMaxLength = configProperties.getInteger("SqlLogParamMaxLength", 20);
1269
1270                        if (sqlLogCacheArray == null) {
1271
1272                                sqlLogCacheArray = new CacheArray();
1273                                long sqlLogFilterTimer = configProperties.getLong("SqlLogTimer", 100L);
1274                                sqlLogCacheArray.filter(new CacheArrayFilter(sqlLogFilterTimer) {
1275                                        @Override
1276                                        public void execute(Integer index, Object o) {
1277                                                try {
1278                                                        String msg = (String) o;
1279                                                        String dateStr = df.format(new Timestamp(System.currentTimeMillis()));
1280                                                        File sqlLogFile = new File(String.format("%s/%s.csv", logFolderPath, dateStr));
1281                                                        File sqlLogFolder = new File(sqlLogFile.getParent());
1282
1283                                                        if (sqlLogFolder.exists()) {
1284                                                                if (!sqlLogFolder.canWrite())
1285                                                                        throw new IOException(String.format("Can not write to log folder '%s'",
1286                                                                                        sqlLogFolder.getAbsolutePath()));
1287                                                        } else {
1288                                                                sqlLogFolder.mkdirs();
1289                                                        }
1290
1291                                                        if (sqlLogFile.exists()) {
1292                                                                if (!sqlLogFile.canWrite())
1293                                                                        throw new IOException(String.format("Can not write to log file '%s'",
1294                                                                                        sqlLogFile.getAbsolutePath()));
1295                                                        }
1296
1297                                                        int suffixIndex = sqlLogFile.getName().lastIndexOf(".");
1298                                                        String logFileNamePrefix = sqlLogFile.getName().substring(0, suffixIndex);
1299                                                        long logSize = FileTools.size(sqlLogFile);
1300                                                        if (!sqlLogFile.exists()) {
1301                                                                FileTools.write(sqlLogFile, header, false);
1302                                                        }
1303                                                        if (logSize < maxFileSize) {
1304                                                                FileTools.write(sqlLogFile, msg, true);
1305                                                        } else {
1306                                                                int logIndex = getLogFileIndex(configProperties, sqlLogFolder, logFileNamePrefix,
1307                                                                                "csv");
1308                                                                if (logIndex == 0) {
1309                                                                        FileTools.write(sqlLogFile, String.format("%s%s", header, msg), false);
1310                                                                } else {
1311                                                                        backupLog(sqlLogFolder, sqlLogFile, logFileNamePrefix, logIndex, header, msg);
1312                                                                }
1313                                                        }
1314                                                        archiveLog(archiveDays, sqlLogFolder);
1315                                                } catch (Exception e) {
1316                                                        LoggerFactory.getLogger(DriverExecutor.class).warn(e.getMessage(), e);
1317                                                }
1318                                        }
1319                                });
1320                        }
1321
1322                        if (logEnable) {
1323                                String hostname = CommonTools.getHostname();
1324                                String dateTimeStr = dtf.format(new Timestamp(System.currentTimeMillis()));
1325                                boolean isOverspend = false;
1326                                boolean isSpendSeq = sql.equals("spend");
1327                                if(isSpendSeq && params != null){
1328                                    long spendValue = Long.parseLong(params + "");
1329                                    isOverspend = (spendValue >= overspend);
1330                                }
1331
1332                String threadId = Thread.currentThread().getName() + "-" + Thread.currentThread().getId();
1333                                String msg = String.format("%s,%s,%s,%s,%s,%s,%s,%s,\"%s\",\"%s\"%s", dateTimeStr, hostname,
1334                                                    threadId, dds.getName(), connHashCode,deHashCode, type, seq,
1335                                                    replaceToSigleLine(sql), handleParams(logParamMaxLength, params), System.lineSeparator());  
1336                                                    
1337                                String key = String.format("%s_%s",deHashCode,connHashCode);
1338                                if(seq <= 0 || overspend <= 0){
1339                                    sqlLogCacheArray.add(msg);
1340                                    SQL_LOG_MSG_MAPPING.remove(key);
1341                                SQL_LOG_OVERSPEND_MAPPING.remove(key);                              
1342                                }
1343                                if(seq > 0 && overspend > 0){
1344                                SQL_LOG_OVERSPEND_MAPPING.put(key,isOverspend);
1345                                String existsMsg = SQL_LOG_MSG_MAPPING.get(key);
1346                                existsMsg = existsMsg == null ? msg : (existsMsg + msg);
1347                                SQL_LOG_MSG_MAPPING.put(key, existsMsg);
1348                                
1349                                boolean seqOverspend = SQL_LOG_OVERSPEND_MAPPING.get(key);
1350                                if(isSpendSeq && seqOverspend){
1351                                    sqlLogCacheArray.add(SQL_LOG_MSG_MAPPING.get(key) + "");
1352                                    SQL_LOG_MSG_MAPPING.remove(key);
1353                                    SQL_LOG_OVERSPEND_MAPPING.remove(key);
1354                                }
1355                                if(isSpendSeq && !seqOverspend){
1356                                    SQL_LOG_MSG_MAPPING.remove(key);
1357                                    SQL_LOG_OVERSPEND_MAPPING.remove(key);
1358                                }
1359                                }
1360                                return true;
1361                        }
1362
1363                }
1364                return false;
1365        }
1366
1367        /**
1368         * Backup log file
1369         * */
1370        private static void backupLog(File logFolder, File logFile, String logFileNamePrefix, int logIndex, String header,
1371                        String msg) throws Exception {
1372                String backupLogFileName = String.format("%s/%s.%s.csv", logFolder.getAbsolutePath(), logFileNamePrefix,
1373                                logIndex);
1374                Path source = Paths.get(logFile.getAbsolutePath());
1375                Path target = Paths.get(backupLogFileName);
1376                Files.move(source, target, StandardCopyOption.REPLACE_EXISTING);
1377                FileTools.write(logFile, String.format("%s%s", header, msg), false);
1378        }
1379
1380        /**
1381         * Get log file index
1382         * For backup log file use
1383         * */
1384        private static int getLogFileIndex(ConfigProperties configProperties, File folder, String prefix, String suffix) {
1385                Integer maxBackupIndex = configProperties.getInteger("SqlLogMaxBackupIndex", 10);
1386                for (int i = 1; i <= maxBackupIndex; i++) {
1387                        String logFileName = String.format("%s/%s.%s.%s", folder.getAbsolutePath(), prefix, i, suffix);
1388                        File logFile = new File(logFileName);
1389                        if (!logFile.exists())
1390                                return i;
1391                }
1392                return 0;
1393        }
1394
1395        /**
1396         * Archive Log
1397         * */
1398        private static void archiveLog(int archiveDays, File sqlLogFolder) {
1399                if (archiveDays > 0) {
1400                        try {
1401                                long archiveDaysMs = new Date().getTime() - (archiveDays * 24 * 3600000L);
1402                                deleteFilesOlderThan(sqlLogFolder, archiveDaysMs);
1403                        } catch (Exception e) {
1404                                LoggerFactory.getLogger(DriverExecutor.class).warn(e);
1405                        }
1406                }
1407        }
1408
1409        /**
1410         * Delete old archive logs
1411         * */
1412        private static void deleteFilesOlderThan(File directory, long archiveDaysMs) throws IOException {
1413                if (directory.isDirectory()) {
1414                        File[] files = directory.listFiles();
1415                        if (files != null) {
1416                                for (File file : files) {
1417                                        if (file.isFile()) {
1418                                                boolean isLogFile = file.getName().toLowerCase().endsWith(".csv");
1419                                                if (isLogFile) {
1420                                                        boolean canWrite = file.canWrite();
1421                                                        if (canWrite) {
1422                                                                long lastModified = file.lastModified();
1423                                                                if (lastModified < archiveDaysMs) {
1424                                                                        Files.deleteIfExists(Paths.get(file.toURI()));
1425                                                                }
1426                                                        }
1427                                                }
1428                                        }
1429                                }
1430                        }
1431                }
1432        }
1433
1434        /**
1435         * Replace to sigle line
1436         * For write csv log
1437         * */
1438        private static String replaceToSigleLine(String msg) {
1439                return CodeEscape.escapeToSingleLineForCsv(msg);
1440        }
1441
1442        /**
1443         * Hahdle Params
1444         * For write csv log
1445         * */
1446        private static String handleParams(int paramMaxLength, Object params) {
1447
1448                if (params == null)
1449                        return "null";
1450
1451                StringBuffer paramSbf = new StringBuffer("");
1452                if (params instanceof List) {
1453                        List<Object> listParams = (List<Object>) params;
1454                        int size = listParams.size();
1455                        for (int i = 0; i < size; i++) {
1456                                Object param = listParams.get(i);
1457                                String str = param + "";
1458                                if (str.length() > paramMaxLength) {
1459                                        paramSbf.append(str.substring(0, paramMaxLength) + "...");
1460                                } else {
1461                                        paramSbf.append(str);
1462                                }
1463                                if (i < size - 1) {
1464                                        paramSbf.append(";");
1465                                }
1466                        }
1467                } else {
1468                        paramSbf.append(params);
1469                }
1470                return replaceToSigleLine(paramSbf.toString());
1471        }
1472
1473        /**For Sync DataSource**/
1474
1475        /**
1476         * For database replication
1477         * */
1478        protected static void callAndReturnBooleanSync(Connection masterConn, long seq, String sql, List<Object> params,
1479                        boolean returnResult) throws SQLException {
1480                if (masterConn instanceof DriverConnection) {
1481                        openSyncConnection(masterConn);
1482
1483                        DriverConnection masterDriverConn = (DriverConnection) masterConn;
1484                        Integer masterConnHashCode = masterDriverConn.getOriginConnectionHashCode();
1485                        ConfigProperties ddscp = masterDriverConn.getDriverDataSource().getConfigProperties();
1486                        boolean connCheck = true;
1487                        boolean returnCheck = true;
1488                        if (ddscp != null) {
1489                                connCheck = ddscp.getBoolean("SyncConnectionCheck", true);
1490                                returnCheck = ddscp.getBoolean("SyncReturnCheck", true);
1491                        }
1492                        List<DriverExecutor> deList = SYNC_EXECUTOR_MARK.get(masterConnHashCode);
1493                        if (deList != null) {
1494                                for (DriverExecutor de : deList) {
1495                                        try {
1496                                                boolean resultSync = de.callAndReturnBoolean(sql, params);
1497                                                if (returnCheck) {
1498                                                        if (resultSync != returnResult) {
1499                                                                writeSqlLog(de.hashCode(),masterDriverConn, "diffed", seq, "diffed", String.format("%s-%s",
1500                                                                                de.getDataSourceName(), de.getOriginConnectionHashCode()));
1501                                                                int errorCode = 99906;
1502                                                                String errorMsg = String.format("The returned results are inconsistent '%s-%s'.",
1503                                                                                de.getDataSourceName(), de.getOriginConnectionHashCode());
1504                                                                throw new SQLException(errorMsg) {
1505                                                                        @Override
1506                                                                        public int getErrorCode() {
1507                                                                                return errorCode;
1508                                                                        }
1509                                                                };
1510                                                        }
1511                                                }
1512                                        } catch (SQLException e) {
1513                                                LoggerFactory.getLogger(DriverExecutor.class)
1514                                                                .warn(String.format("ERROR CODE: (%s) %s", e.getErrorCode(), e.getMessage()));
1515                                                if (connCheck)
1516                                                        throw e;
1517                                        }
1518                                }
1519                        }
1520                }
1521        }
1522
1523        /**
1524         * For database replication
1525         * */
1526        protected static void callAndReturnListSync(Connection masterConn, int cursorStart, int maxRows, String sql,
1527                        List<Object> params) throws SQLException {
1528                if (masterConn instanceof DriverConnection) {
1529                        openSyncConnection(masterConn);
1530
1531                        DriverConnection masterDriverConn = (DriverConnection) masterConn;
1532                        Integer masterConnHashCode = masterDriverConn.getOriginConnectionHashCode();
1533
1534                        List<DriverExecutor> deList = SYNC_EXECUTOR_MARK.get(masterConnHashCode);
1535                        if (deList != null) {
1536                                for (DriverExecutor de : deList) {
1537                                        try {
1538                                                de.callAndReturnBoolean(sql, params);
1539                                        } catch (SQLException e) {
1540                                                LoggerFactory.getLogger(DriverExecutor.class)
1541                                                                .warn(String.format("ERROR CODE: (%s) %s", e.getErrorCode(), e.getMessage()));
1542                                                ConfigProperties ddscp = masterDriverConn.getDriverDataSource().getConfigProperties();
1543                                                boolean connCheck = true;
1544                                                if (ddscp != null) {
1545                                                        connCheck = ddscp.getBoolean("SyncConnectionCheck", true);
1546                                                }
1547                                                if (connCheck)
1548                                                        throw e;
1549                                        }
1550                                }
1551                        }
1552                }
1553        }
1554
1555        /**
1556         * For database replication
1557         * */
1558        protected static void callAndReturnRowsSync(Connection masterConn, long seq, String sql, List<Object> params,
1559                        int returnRows) throws SQLException {
1560                if (masterConn instanceof DriverConnection) {
1561                        openSyncConnection(masterConn);
1562
1563                        DriverConnection masterDriverConn = (DriverConnection) masterConn;
1564                        Integer masterConnHashCode = masterDriverConn.getOriginConnectionHashCode();
1565                        ConfigProperties ddscp = masterDriverConn.getDriverDataSource().getConfigProperties();
1566                        boolean connCheck = true;
1567                        boolean returnCheck = true;
1568                        if (ddscp != null) {
1569                                connCheck = ddscp.getBoolean("SyncConnectionCheck", true);
1570                                returnCheck = ddscp.getBoolean("SyncReturnCheck", true);
1571                        }
1572                        List<DriverExecutor> deList = SYNC_EXECUTOR_MARK.get(masterConnHashCode);
1573                        if (deList != null) {
1574                                for (DriverExecutor de : deList) {
1575                                        try {
1576                                                int rowSync = de.callAndReturnRows(sql, params);
1577                                                if (returnCheck) {
1578                                                        if (rowSync != returnRows) {
1579                                                                writeSqlLog(de.hashCode(),masterDriverConn, "diffed", seq, "diffed", String.format("%s-%s",
1580                                                                                de.getDataSourceName(), de.getOriginConnectionHashCode()));
1581                                                                int errorCode = 99906;
1582                                                                String errorMsg = String.format("The returned results are inconsistent '%s-%s'.",
1583                                                                                de.getDataSourceName(), de.getOriginConnectionHashCode());
1584                                                                throw new SQLException(errorMsg) {
1585                                                                        @Override
1586                                                                        public int getErrorCode() {
1587                                                                                return errorCode;
1588                                                                        }
1589                                                                };
1590                                                        }
1591                                                }
1592                                        } catch (SQLException e) {
1593                                                LoggerFactory.getLogger(DriverExecutor.class)
1594                                                                .warn(String.format("ERROR CODE: (%s) %s", e.getErrorCode(), e.getMessage()));
1595                                                if (connCheck)
1596                                                        throw e;
1597                                        }
1598                                }
1599                        }
1600                }
1601        }
1602
1603        /**
1604         * For database replication
1605         * */
1606        protected static void executeBatchListSync(Connection masterConn, long seq, String sql, List<List<Object>> records,
1607                        int returnRows) throws SQLException {
1608                if (masterConn instanceof DriverConnection) {
1609                        openSyncConnection(masterConn);
1610
1611                        DriverConnection masterDriverConn = (DriverConnection) masterConn;
1612                        Integer masterConnHashCode = masterDriverConn.getOriginConnectionHashCode();
1613                        ConfigProperties ddscp = masterDriverConn.getDriverDataSource().getConfigProperties();
1614                        boolean connCheck = true;
1615                        boolean returnCheck = true;
1616                        if (ddscp != null) {
1617                                connCheck = ddscp.getBoolean("SyncConnectionCheck", true);
1618                                returnCheck = ddscp.getBoolean("SyncReturnCheck", true);
1619                        }
1620                        List<DriverExecutor> deList = SYNC_EXECUTOR_MARK.get(masterConnHashCode);
1621                        if (deList != null) {
1622                                for (DriverExecutor de : deList) {
1623                                        try {
1624                                                int rowSync = de.executeBatchList(sql, records);
1625                                                if (returnCheck) {
1626                                                        if (rowSync != returnRows) {
1627                                                                writeSqlLog(de.hashCode(),masterDriverConn, "diffed", seq, "diffed", String.format("%s-%s",
1628                                                                                de.getDataSourceName(), de.getOriginConnectionHashCode()));
1629                                                                int errorCode = 99906;
1630                                                                String errorMsg = String.format("The returned results are inconsistent '%s-%s'.",
1631                                                                                de.getDataSourceName(), de.getOriginConnectionHashCode());
1632                                                                throw new SQLException(errorMsg) {
1633                                                                        @Override
1634                                                                        public int getErrorCode() {
1635                                                                                return errorCode;
1636                                                                        }
1637                                                                };
1638                                                        }
1639                                                }
1640                                        } catch (SQLException e) {
1641                                                LoggerFactory.getLogger(DriverExecutor.class)
1642                                                                .warn(String.format("ERROR CODE: (%s) %s", e.getErrorCode(), e.getMessage()));
1643                                                if (connCheck)
1644                                                        throw e;
1645                                        }
1646                                }
1647                        }
1648                }
1649        }
1650
1651        /**
1652         * For database replication
1653         * */
1654        protected static void executeSync(Connection masterConn, long seq, String sql, List<Object> params, int returnRows)
1655                        throws SQLException {
1656                if (masterConn instanceof DriverConnection) {
1657                        openSyncConnection(masterConn);
1658
1659                        DriverConnection masterDriverConn = (DriverConnection) masterConn;
1660                        Integer masterConnHashCode = masterDriverConn.getOriginConnectionHashCode();
1661                        ConfigProperties ddscp = masterDriverConn.getDriverDataSource().getConfigProperties();
1662                        boolean connCheck = true;
1663                        boolean returnCheck = true;
1664                        if (ddscp != null) {
1665                                connCheck = ddscp.getBoolean("SyncConnectionCheck", true);
1666                                returnCheck = ddscp.getBoolean("SyncReturnCheck", true);
1667                        }
1668                        List<DriverExecutor> deList = SYNC_EXECUTOR_MARK.get(masterConnHashCode);
1669                        if (deList != null) {
1670                                for (DriverExecutor de : deList) {
1671                                        try {
1672                                                int rowSync = de.execute(sql, params);
1673                                                if (returnCheck) {
1674                                                        if (rowSync != returnRows) {
1675                                                                writeSqlLog(de.hashCode(),masterDriverConn, "diffed", seq, "diffed", String.format("%s-%s",
1676                                                                                de.getDataSourceName(), de.getOriginConnectionHashCode()));
1677                                                                int errorCode = 99906;
1678                                                                String errorMsg = String.format("The returned results are inconsistent '%s-%s'.",
1679                                                                                de.getDataSourceName(), de.getOriginConnectionHashCode());
1680                                                                throw new SQLException(errorMsg) {
1681                                                                        @Override
1682                                                                        public int getErrorCode() {
1683                                                                                return errorCode;
1684                                                                        }
1685                                                                };
1686                                                        }
1687                                                }
1688                                        } catch (SQLException e) {
1689                                                LoggerFactory.getLogger(DriverExecutor.class)
1690                                                                .warn(String.format("ERROR CODE: (%s) %s", e.getErrorCode(), e.getMessage()));
1691                                                if (connCheck)
1692                                                        throw e;
1693                                        }
1694                                }
1695                        }
1696                }
1697        }
1698
1699        /**
1700         * For database replication
1701         * */
1702        protected synchronized static boolean openSyncConnection(Connection masterConn) throws SQLException {
1703                if (masterConn instanceof DriverConnection) {
1704
1705                        DriverConnection masterDriverConn = (DriverConnection) masterConn;
1706                        Integer masterConnHashCode = masterDriverConn.getOriginConnectionHashCode();
1707
1708                        boolean isExist = SYNC_EXECUTOR_MARK.containsKey(masterConnHashCode);
1709
1710                        if (isExist) {
1711                                return false;
1712                        } else {
1713                                SYNC_EXECUTOR_MARK.put(masterConnHashCode, new ArrayList<DriverExecutor>());
1714                        }
1715
1716                        DriverDataSource dds = masterDriverConn.getDriverDataSource();
1717                        if (dds != null) {
1718                                List<DriverDataSource> sdsl = dds.getSyncDataSourceList(dds.getName());
1719                                if (sdsl != null) {
1720
1721                                        if (sdsl.isEmpty())
1722                                                return false;
1723
1724                                        for (DriverDataSource sds : sdsl) {
1725
1726                                                Integer sdsHashCode = sds.hashCode();
1727                                                if (!SYNC_CONN_ERROR_TIME.containsKey(sdsHashCode)) {
1728                                                        SYNC_CONN_ERROR_TIME.put(sdsHashCode, 0L);
1729                                                }
1730
1731                                                Long syncConnErrorTime = SYNC_CONN_ERROR_TIME.get(sdsHashCode);
1732                                                if (syncConnErrorTime > 0) {
1733                                                        ConfigProperties ddscp = dds.getConfigProperties();
1734                                                        long connCheckMs = 10000L;
1735                                                        if (ddscp != null) {
1736                                                                connCheckMs = ddscp.getLong("SyncConnectionCheckTime", 10000L);
1737                                                        }
1738                                                        boolean isSkipConn = syncConnErrorTime > 0
1739                                                                        && (System.currentTimeMillis() - syncConnErrorTime) <= connCheckMs;
1740                                                        if (isSkipConn) {
1741                                                                continue;
1742                                                        }
1743                                                }
1744
1745                                                String masterFingerprint = dds.getFingerprint();
1746                                                String syncFingerprint = sds.getFingerprint();
1747                                                if (masterFingerprint.equalsIgnoreCase(syncFingerprint)) {
1748                                                        LoggerFactory.getLogger(DriverExecutor.class)
1749                                                                        .warn("Skip sync reason 'same connection fingerprint'.");
1750                                                        continue;
1751                                                }
1752
1753                                                try {
1754                                                        final Connection conn = sds.getConnection();
1755                                                        if (conn == null) {
1756                                                                int errorCode = 99904;
1757                                                                String errorMsg = "Connection is null.";
1758                                                                throw new SQLException(errorMsg) {
1759                                                                        @Override
1760                                                                        public int getErrorCode() {
1761                                                                                return errorCode;
1762                                                                        }
1763                                                                };
1764                                                        }
1765                                                        conn.setAutoCommit(masterConn.getAutoCommit());
1766                                                        SYNC_CONN_ERROR_TIME.put(sdsHashCode, 0L);
1767                                                        SYNC_EXECUTOR_MARK.get(masterConnHashCode).add(new DriverExecutor(conn));
1768                                                } catch (SQLException e) {
1769                                                        SYNC_CONN_ERROR_TIME.put(sdsHashCode, System.currentTimeMillis());
1770                                                        LoggerFactory.getLogger(DriverExecutor.class)
1771                                                                        .warn(String.format("ERROR CODE: (%s) %s", e.getErrorCode(), e.getMessage()));
1772                                                        ConfigProperties ddscp = dds.getConfigProperties();
1773                                                        boolean connCheck = true;
1774                                                        if (ddscp != null) {
1775                                                                connCheck = ddscp.getBoolean("SyncConnectionCheck", true);
1776                                                        }
1777                                                        if (connCheck)
1778                                                                throw e;
1779                                                }
1780
1781                                        }
1782
1783                                        return true;
1784                                }
1785                        }
1786                }
1787                return false;
1788        }
1789
1790        /**
1791         * For database replication
1792         * */
1793        protected static void closeSyncConnection(Connection masterConn) throws SQLException {
1794                if (masterConn instanceof DriverConnection) {
1795                        DriverConnection masterDriverConn = (DriverConnection) masterConn;
1796                        Integer masterConnHashCode = masterDriverConn.getOriginConnectionHashCode();
1797
1798                        List<DriverExecutor> deList = SYNC_EXECUTOR_MARK.get(masterConnHashCode);
1799                        if (deList != null) {
1800                                for (DriverExecutor de : deList) {
1801                                        try {
1802                                                de.close();
1803                                        } catch (SQLException e) {
1804                                                LoggerFactory.getLogger(DriverExecutor.class)
1805                                                                .warn(String.format("ERROR CODE: (%s) %s", e.getErrorCode(), e.getMessage()));
1806                                                ConfigProperties ddscp = masterDriverConn.getDriverDataSource().getConfigProperties();
1807                                                boolean connCheck = true;
1808                                                if (ddscp != null) {
1809                                                        connCheck = ddscp.getBoolean("SyncConnectionCheck", true);
1810                                                }
1811                                                if (connCheck)
1812                                                        throw e;
1813                                        }
1814                                }
1815                                SYNC_EXECUTOR_MARK.remove(masterConnHashCode);
1816                                LoggerFactory.getLogger(DriverExecutor.class).debug("CloseSyncConnection - masterConn '{}' finished.",
1817                                                masterConnHashCode);
1818                        }
1819                }
1820        }
1821
1822        /**
1823         * For database replication
1824         * */
1825        protected static void commitSyncConnection(Connection masterConn) throws SQLException {
1826                if (masterConn instanceof DriverConnection) {
1827                        DriverConnection masterDriverConn = (DriverConnection) masterConn;
1828                        Integer masterConnHashCode = masterDriverConn.getOriginConnectionHashCode();
1829
1830                        List<DriverExecutor> deList = SYNC_EXECUTOR_MARK.get(masterConnHashCode);
1831                        if (deList != null) {
1832                                for (DriverExecutor de : deList) {
1833                                        try {
1834                                                de.commit();
1835                                        } catch (SQLException e) {
1836                                                LoggerFactory.getLogger(DriverExecutor.class)
1837                                                                .warn(String.format("ERROR CODE: (%s) %s", e.getErrorCode(), e.getMessage()));
1838                                                ConfigProperties ddscp = masterDriverConn.getDriverDataSource().getConfigProperties();
1839                                                boolean connCheck = true;
1840                                                if (ddscp != null) {
1841                                                        connCheck = ddscp.getBoolean("SyncConnectionCheck", true);
1842                                                }
1843                                                if (connCheck)
1844                                                        throw e;
1845                                        }
1846                                }
1847                                LoggerFactory.getLogger(DriverExecutor.class).debug("CommitSyncConnection - masterConn '{}' finished.",
1848                                                masterConnHashCode);
1849                        }
1850                }
1851        }
1852
1853        /**
1854         * For database replication
1855         * */
1856        protected static void rollbackSyncConnection(Connection masterConn) throws SQLException {
1857                if (masterConn instanceof DriverConnection) {
1858                        DriverConnection masterDriverConn = (DriverConnection) masterConn;
1859                        Integer masterConnHashCode = masterDriverConn.getOriginConnectionHashCode();
1860
1861                        List<DriverExecutor> deList = SYNC_EXECUTOR_MARK.get(masterConnHashCode);
1862                        if (deList != null) {
1863                                for (DriverExecutor de : deList) {
1864                                        try {
1865                                                de.rollback();
1866                                        } catch (SQLException e) {
1867                                                LoggerFactory.getLogger(DriverExecutor.class)
1868                                                                .warn(String.format("ERROR CODE: (%s) %s", e.getErrorCode(), e.getMessage()));
1869                                                ConfigProperties ddscp = masterDriverConn.getDriverDataSource().getConfigProperties();
1870                                                boolean connCheck = true;
1871                                                if (ddscp != null) {
1872                                                        connCheck = ddscp.getBoolean("SyncConnectionCheck", true);
1873                                                }
1874                                                if (connCheck)
1875                                                        throw e;
1876                                        }
1877                                }
1878                                LoggerFactory.getLogger(DriverExecutor.class)
1879                                                .debug("RollbackSyncConnection - masterConn '{}' finished.", masterConnHashCode);
1880                        }
1881                }
1882        }
1883
1884        /**
1885         * For database replication
1886         * */
1887        protected static void abortSyncConnection(Connection masterConn) throws SQLException {
1888                if (masterConn instanceof DriverConnection) {
1889                        DriverConnection masterDriverConn = (DriverConnection) masterConn;
1890                        Integer masterConnHashCode = masterDriverConn.getOriginConnectionHashCode();
1891
1892                        List<DriverExecutor> deList = SYNC_EXECUTOR_MARK.get(masterConnHashCode);
1893                        if (deList != null) {
1894                                for (DriverExecutor de : deList) {
1895                                        try {
1896                                                de.abort();
1897                                        } catch (SQLException e) {
1898                                                LoggerFactory.getLogger(DriverExecutor.class)
1899                                                                .warn(String.format("ERROR CODE: (%s) %s", e.getErrorCode(), e.getMessage()));
1900                                                ConfigProperties ddscp = masterDriverConn.getDriverDataSource().getConfigProperties();
1901                                                boolean connCheck = true;
1902                                                if (ddscp != null) {
1903                                                        connCheck = ddscp.getBoolean("SyncConnectionCheck", true);
1904                                                }
1905                                                if (connCheck)
1906                                                        throw e;
1907                                        }
1908                                }
1909                                LoggerFactory.getLogger(DriverExecutor.class).debug("AbortSyncConnection - masterConn '{}' finished.",
1910                                                masterConnHashCode);
1911                        }
1912                }
1913        }
1914
1915}