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.io.PrintWriter;
027import java.sql.Connection;
028import java.sql.ConnectionBuilder;
029import java.sql.SQLException;
030import javax.sql.DataSource;
031import java.sql.SQLFeatureNotSupportedException;
032
033import com.killcoding.log.Logger;
034import com.killcoding.log.LoggerFactory;
035
036import java.util.Properties;
037import java.util.concurrent.Executors;
038import java.io.File;
039import java.net.URLClassLoader;
040import java.lang.reflect.Constructor;
041import java.net.URL;
042import java.sql.Driver;
043import java.util.Map;
044import java.util.concurrent.ConcurrentHashMap;
045import java.sql.DriverManager;
046import java.util.Iterator;
047import java.util.Date;
048import java.util.List;
049import java.util.ArrayList;
050import java.util.Collections;
051import java.util.Set;
052import com.killcoding.tool.ConfigProperties;
053import com.killcoding.tool.FileTools;
054import java.io.Reader;
055import java.io.IOException;
056import java.io.StringReader;
057import java.util.concurrent.ExecutorService;
058import com.killcoding.tool.CommonTools;
059import java.text.SimpleDateFormat;
060import java.util.stream.Stream;
061import java.util.Comparator;
062import java.util.concurrent.Callable;
063import java.util.concurrent.Future;
064import java.util.concurrent.Executor;
065import java.util.HashMap;
066import java.nio.file.Files;
067import java.nio.file.Paths;
068import java.util.Enumeration;
069
070/**
071 *  Implement and enhance the datasource class
072 *  Support write connection status log
073 *  Support abort tiemout connection
074 *  Support failover
075 * */
076public final class DriverDataSource implements DataSource {
077
078        private final Logger log = LoggerFactory.getLogger(DriverDataSource.class);
079        
080        public static Long RELOAD_TIMER = 60000L;
081
082        private String DATASOURCE_ID = null;
083        private final long PID = ProcessHandle.current().pid();
084
085        private long countCreatedConnection = 0L;
086        private long countAbortedIdle = 0L;
087        private long countAbortedTimeout = 0L;
088        private long countAbortedLifeCycle = 0L;
089        private long countAbortedInvalid = 0L;
090        private long countAbortedCloseFailed = 0L;
091        private long countClosed = 0L;
092        private double maxUsage = 0.0D;
093        
094        private final Map<String, Driver> DRIVER_MARK = new ConcurrentHashMap<String, Driver>();
095        private final Map<Integer, Long> CONN_ERROR_TIME = new ConcurrentHashMap<Integer, Long>();
096
097        private boolean inited = false;
098
099        private Boolean dynamicReloadConfig = false;
100        private Boolean enableStatusLog = false;
101        private Boolean enableFullStatusLog = true;
102        private Integer statusLogArchiveDays = 30;
103        private String statusLogFolder = null;
104        private File statusLogFile = null;
105        private Integer maxAveUsageSize = 10000;
106
107    private final List<Double> aveUsageSum = new ArrayList<Double>();
108        private final List<String> urlList = new ArrayList<String>();
109
110        private int urlIndex = 0;
111
112        private Integer maxPoolSize = Integer.MAX_VALUE;
113        private Integer minPoolSize = 0;
114        private Long lifeCycle = Long.MAX_VALUE;
115        private Long idleTimeout = 60L;
116        private Long timeout = 0L;
117        private Long transactionTimeout = 0L;
118        private Long refreshPoolTimer = 1000L;
119        private Long testValidTimeout = 5L;
120
121        private ConfigProperties configProperties = null;
122        private String driverJar;
123        private String connProps;
124        private String driverClassName;
125        private String url;
126        private String username;
127        private String password;
128        private String name = "DriverDataSource";
129        private String abortMode = "abort";
130        private String appDataSourcePath;
131
132        private boolean useExternalConfig = false;
133        private boolean loadedConfig = false;
134        private boolean closed = false;
135
136        private File appDataSource = null;
137        private String owner = null;
138        private List<DriverDataSource> syncDriverDataSource = null;
139
140        private final ConnectionPool connectionPool = new ConnectionPool();
141        private final ExecutorService connectionPoolExecutor = Executors.newFixedThreadPool(1);
142        
143        private long dynamicReloadConfigTime = System.currentTimeMillis();
144        
145        /**
146         * New a DriverDataSource object
147         * Use env APP_DATASOURCE=/path/DataSource.properties
148         * */
149        public DriverDataSource() {
150                super();
151                this.appDataSource = CommonTools.getSystemProperties(DriverDataSource.class, "APP_DATASOURCE",
152                                "DataSource.properties");
153                if (this.appDataSource == null) {
154                        this.appDataSource = CommonTools.getSystemProperties(DriverDataSource.class, "APP_DATASOURCE",
155                                        "Datasource.properties");
156                }
157
158                if (this.appDataSource != null) {
159                        this.appDataSourcePath = this.appDataSource.getAbsolutePath();
160                }
161
162                initDs();
163                setDynamicReloadConfig(true);
164                reloadConfig();
165        }
166
167        /**
168         * New a DriverDataSource object
169         * @param appDataSource - It is file object (e.g. /your_path/DataSource.properties) 
170         * */
171        public DriverDataSource(File appDataSource) {
172                super();
173                this.appDataSource = appDataSource;
174
175                if (this.appDataSource != null) {
176                    if(!this.appDataSource.exists()){
177                        this.appDataSourcePath = this.appDataSource.getAbsolutePath();
178                        Logger.systemError(DriverDataSource.class, "The '{}' does not exists.",this.appDataSourcePath);
179                    }
180                        
181                }
182
183                initDs();
184                setDynamicReloadConfig(true);
185                reloadConfig();
186        }
187
188        /**
189         * New a DriverDataSource object
190         * @param configPropertiesContent - It is properties context
191         * */
192        public DriverDataSource(String configPropertiesContent) throws IOException {
193                super();
194                initDs();
195                setDynamicReloadConfig(false);
196                setConfig(configPropertiesContent);
197        }
198
199        /**
200         * New a DriverDataSource object
201         * @param configProperties - It is ConfigProperties object
202         * */
203        public DriverDataSource(ConfigProperties configProperties) throws IOException {
204                super();
205                initDs();
206                setDynamicReloadConfig(false);
207                setConfig(configProperties);
208        }
209
210        private void initDs() {
211                SimpleDateFormat datasourceIdFormat = new SimpleDateFormat("yyyyMMddHHmmssSSS");
212                DATASOURCE_ID = String.format("%s.%s.%s", datasourceIdFormat.format(new Date()), PID,
213                                CommonTools.generateId(4).toUpperCase());
214        }
215
216        /**
217         * Set app datasource properties file path
218         * @param appDataSourcePath - file path
219         * */
220        public void setAppDataSourcePath(String appDataSourcePath) {
221                if (!CommonTools.isBlank(appDataSourcePath)) {
222                        this.appDataSource = new File(appDataSourcePath);
223                        this.appDataSourcePath = this.appDataSource.getAbsolutePath();
224                }
225        }
226
227        /**
228         * Get app datasource properties file path
229         * */
230        public String getAppDataSourcePath() {
231                return this.appDataSourcePath;
232        }
233
234        /**
235         * Check is loaded config
236         * @return boolean
237         * */
238        public boolean isLoadedConfig() {
239                return loadedConfig;
240        }
241
242        /**
243         * Set enable status log
244         * @param enableStatusLog - 'true' is enable write status log
245         * */
246        public void setEnableStatusLog(Boolean enableStatusLog) {
247                this.enableStatusLog = enableStatusLog == null ? false : enableStatusLog;
248        }
249
250        /**
251         * Set enable full status log
252         * @param enableFullStatusLog - 'true' is enable write status log when full connections
253         * */
254        public void setEnableFullStatusLog(Boolean enableFullStatusLog) {
255                this.enableFullStatusLog = enableFullStatusLog == null ? true : enableFullStatusLog;
256        }
257
258        /**
259         * Set status log archive days
260         * @param  statusLogArchiveDays - Delete before x days log
261         * */
262        public void setStatusLogArchiveDays(Integer statusLogArchiveDays) {
263                this.statusLogArchiveDays = statusLogArchiveDays == null ? 30 : statusLogArchiveDays;
264        }
265
266        /**
267         * Set status log target folder
268         * @param _statusLogFolder - Target folder
269         * */
270        public void setStatusLogFolder(String _statusLogFolder) {
271                this.statusLogFolder = CommonTools.isBlank(_statusLogFolder) ? null : _statusLogFolder;
272                if (this.statusLogFolder == null) {
273                        statusLogFile = new File(String.format("%s/DriverDataSource_Status/%s.log",
274                                        CommonTools.getJarPath(DriverDataSource.class), DATASOURCE_ID));
275                } else {
276                        statusLogFile = new File(String.format("%s/%s.log", statusLogFolder, DATASOURCE_ID));
277                }
278        }
279
280        /**
281         * Set connection life cycle 
282         * @param lifeCycle - Unit is ms
283         * */
284        public void setLifeCycle(Long lifeCycle) {
285                this.lifeCycle = (lifeCycle == null ? Long.MAX_VALUE : lifeCycle);
286        }
287
288        /**
289         * Get connection life cycle 
290         * @return Long - Unit is ms
291         * */
292        public Long getLifeCycle() {
293                return this.lifeCycle;
294        }
295
296        /**
297         * Set dynamic reload config
298         * @param dynamicReloadConfig - 'true' is enable dynamic reload config
299         * */
300        public void setDynamicReloadConfig(Boolean dynamicReloadConfig) {
301                this.dynamicReloadConfig = dynamicReloadConfig == null ? false : dynamicReloadConfig;
302        }
303
304        /**
305         * Get dynamic reload config
306         * @return Boolean
307         * */
308        public Boolean getDynamicReloadConfig() {
309                return this.dynamicReloadConfig;
310        }
311
312        /**
313         * Set refresh pool timer
314         * @param refreshPoolTimer - Unit is ms
315         * */
316        public void setRefreshPoolTimer(Long refreshPoolTimer) {
317                this.refreshPoolTimer = (refreshPoolTimer == null ? 1000L : refreshPoolTimer);
318        }
319
320        /**
321         * Get refresh pool timer
322         * @return Long - Unit is ms
323         * */
324        public Long getRefreshPoolTimer() {
325                return this.refreshPoolTimer;
326        }
327
328        /**
329         * Set test valid timeout
330         * @param testValidTimeout Unit is seconds,default is 5s
331         * */
332        public void setTestValidTimeout(Long testValidTimeout) {
333                this.testValidTimeout = (testValidTimeout == null ? 5L : testValidTimeout);
334        }
335
336        /**
337         * Get test valid timeout
338         * @retrn Long - Unit is seconds
339         * */
340        public Long getTestValidTimeout() {
341                return this.testValidTimeout;
342        }
343
344        /**
345         * Set idle timeout
346         * @param idleTimeout - Unit is seconds, default is 60s
347         * */
348        public void setIdleTimeout(Long idleTimeout) {
349                this.idleTimeout = (idleTimeout == null ? 60L : idleTimeout);
350        }
351
352        /**
353         * Get idle timeout
354         * @return Long - Unit is seconds
355         * */
356        public Long getIdleTimeout() {
357                return this.idleTimeout;
358        }
359
360        /**
361         * Set execute timeout
362         * Default is 0s
363         * Disabled timeou if value is 0s
364         * @param timeout - Unit is seconds, 
365         * */
366        public void setTimeout(Long timeout) {
367                this.timeout = (timeout == null ? 0L : timeout);
368        }
369
370        /**
371         * Get execute timeout
372         * @return Long - Unit is seconds, 
373         * */
374        public Long getTimeout() {
375                return this.timeout;
376        }
377
378        /**
379         * Set execute transaction timeout
380         * Default is 0s
381         * Disabled timeou if value is 0s
382         * @param idleTimeout - Unit is seconds, 
383         * */
384        public void setTransactionTimeout(Long transactionTimeout) {
385                this.transactionTimeout = (transactionTimeout == null ? 0L : transactionTimeout);
386        }
387
388        /**
389         * Get execute transaction timeout
390         * @return Long - Unit is seconds, 
391         * */
392        public Long getTransactionTimeout() {
393                return this.transactionTimeout;
394        }
395
396        /**
397         * Set max connection pool size
398         * @param maxPoolSize
399         * */
400        public void setMaxPoolSize(Integer maxPoolSize) {
401                this.maxPoolSize = (maxPoolSize == null ? Integer.MAX_VALUE : maxPoolSize);
402        }
403
404        /**
405         * Get max connection pool size
406         * @return Integer
407         * */
408        public Integer getMaxPoolSize() {
409                return this.maxPoolSize;
410        }
411
412        /**
413         * Set jdbc driver jar path
414         * @param driverJar - Driver jar file or folder path
415         * */
416        public void setDriverJar(String driverJar) {
417                this.driverJar = driverJar;
418        }
419
420        /**
421         * Get jdbc driver jar path
422         * @return String
423         * */
424        public String getDriverJar() {
425                return this.driverJar;
426        }
427
428        /**
429         * Set connection properties
430         * @param connProps -  Sample: autoReconnection=true;key=value
431         * */
432        public void setConnProps(String connProps) {
433                this.connProps = connProps;
434        }
435
436        /**
437         * Get connection properties
438         * @return connProps -  Sample: autoReconnection=true;key=value
439         * */
440        public String getConnProps() {
441                return this.connProps;
442        }
443
444        /**
445         * Set driver class name
446         * @param driverClassName
447         * */
448        public void setDriverClassName(String driverClassName) {
449                this.driverClassName = driverClassName;
450        }
451
452        /**
453         * Get driver class name
454         * @return String
455         * */
456        public String getDriverClassName() {
457                return this.driverClassName;
458        }
459
460        /**
461         * Set connection url
462         * @param urlOrList - String or List
463         * */
464        public void setUrl(Object urlOrList) {
465                if (urlOrList instanceof String) {
466                        this.urlList.add((String) urlOrList);
467                        this.urlIndex = 0;
468                        this.url = this.urlList.get(this.urlIndex);
469                }
470                if (urlOrList instanceof List) {
471                        this.urlList.addAll((List) urlOrList);
472                        this.urlIndex = 0;
473                        this.url = this.urlList.get(this.urlIndex);
474                }
475        }
476
477        /**
478         * Get connection url
479         * */
480        public String getUrl() {
481                return this.url;
482        }
483
484        /**
485         * Set min connection pool size
486         * @param minPoolSize
487         * */
488        public void setMinPoolSize(Integer minPoolSize) {
489                this.minPoolSize = (minPoolSize == null ? 0 : minPoolSize);
490        }
491
492        /**
493         * Get min connection pool size
494         * */
495        public Integer getMinPoolSize() {
496                return this.minPoolSize;
497        }
498
499        /**
500         * Set abort mode
501         * @param abortMode - 'close' or 'abort', default is 'abort'
502         * */
503        public void setAbortMode(String abortMode) {
504                if (abortMode == null)
505                        this.abortMode = "abort";
506        }
507
508        /**
509         * Get abort mode
510         * @return abortMode - 'close' or 'abort', default is 'abort'
511         * */
512        public String getAbortMode() {
513                return this.abortMode;
514        }
515
516        /**
517         * Set thread name
518         * @param name - It is thread name, default is 'DriverDataSource'
519         * */
520        public void setName(String name) {
521                if (name != null)
522                        this.name = name;
523        }
524
525        /**
526         * Get thread name
527         * @return String
528         * */
529        public String getName() {
530                return this.name;
531        }
532
533        /**
534         * Set database username
535         * @param username - Database username
536         * */
537        public void setUsername(String username) {
538                this.username = username;
539        }
540
541        /**
542         * Set database password
543         * @param password - Database password
544         * */
545        public void setPassword(String password) {
546                this.password = password;
547        }
548
549        private synchronized Integer getOpenConnectionSize() {
550                return connectionPool.getOpenConnectionSize();
551        }
552
553
554        /**
555         * Get connection pool size
556         * @return Integer
557         * */
558        public ConnectionPool getConnectionPool() {
559                return connectionPool;
560        }       
561
562        /**
563         * Abort timeout connection
564         * Skip execute when 'timeout' is 0 or 'transactionTimeout' is 0
565         * */
566        private synchronized void abortTimeoutConnection() {
567                try {
568                        if (timeout > 0 || transactionTimeout > 0) {
569                                Date currentTime = new Date();
570                                List<Integer> abortList = new ArrayList<Integer>();
571                                Set<Integer> hashCodeList = connectionPool.getConnectionOpenedAtMark().keySet();
572                                Map<Integer,Date> usingAtMark = connectionPool.getConnectionUsingAtMark();
573                                for (Integer hashCode : hashCodeList) {
574                                        Date openedAt = usingAtMark.get(hashCode);
575                                        if (openedAt != null) {
576                                                Connection conn = connectionPool.getConnectionMark().get(hashCode);
577
578                                                if (conn == null) {
579                                                        connectionPool.removeConnectionUsingAtMark(hashCode);
580                                                        continue;
581                                                }
582
583                                                long timeoutSec = (currentTime.getTime() - openedAt.getTime()) / 1000;
584                                                Long _timeout = conn.getAutoCommit() ? timeout : transactionTimeout;
585
586                                                if (_timeout == 0)
587                                                        continue;
588
589                                                if (timeoutSec >= _timeout) {
590                                                        if (!abortList.contains(hashCode))
591                                                                abortList.add(hashCode);
592                                                }
593                                        }
594                                }
595                                for (Integer hashCode : abortList) {
596                                        countAbortedTimeout++;
597                                        log.mark("AbortTimeoutConnection '{}', ConnectionPoolSize '{}', OpenConnectionSize '{}'.",hashCode,connectionPool.size(),getOpenConnectionSize());
598                                        abort(hashCode);
599                                }
600                        }
601                } catch (Exception e) {
602                        log.warn(e.getMessage(), e);
603                }
604        }
605
606        /**
607         * Abort life cycle connection
608         * Skip execute when 'lifeCycle' is 0
609         * */
610        private synchronized void abortLifeCycleEndedConnection() {
611                if (lifeCycle > 0) {
612                        Date currentTime = new Date();
613                        List<Integer> abortList = new ArrayList<Integer>();
614                        Map<Integer,Date> openedAtMark = connectionPool.getConnectionOpenedAtMark();
615                        Set<Integer> hashCodeList = openedAtMark.keySet();
616                        for (Integer hashCode : hashCodeList) {
617                                Date openedAt = openedAtMark.get(hashCode);
618                                if (openedAt != null) {
619                                        long lifeSec = (currentTime.getTime() - openedAt.getTime()) / 1000;
620                                        if (lifeSec >= lifeCycle && connectionPool.contains(hashCode)) {
621                                                if (!abortList.contains(hashCode))
622                                                        abortList.add(hashCode);
623                                        }
624                                }
625                        }
626                        for (Integer hashCode : abortList) {
627                                if (connectionPool.contains(hashCode)) {
628                                        countAbortedLifeCycle++;
629                                        log.mark("AbortLifeCycleEndedConnection '{}', ConnectionPoolSize '{}', OpenConnectionSize '{}'.",hashCode,connectionPool.size(),getOpenConnectionSize());
630                                        abort(hashCode);
631                                }
632                        }
633                }
634        }
635
636        /**
637         * Abort idle timeout connection
638         * Skip execute when 'idleTimeout' is 0
639         * */
640        private synchronized void abortIdleTimeoutConnection() {
641                if (idleTimeout > 0) {
642                        Date currentTime = new Date();
643                        List<Integer> abortList = new ArrayList<Integer>();
644                        Map<Integer,Date> idleBeginMark = connectionPool.getConnectionIdleBeginMark();
645                        Set<Integer> hashCodeList = idleBeginMark.keySet();
646                        for (Integer hashCode : hashCodeList) {
647                                Date openedAt = idleBeginMark.get(hashCode);
648                                if (openedAt != null) {
649                                        long idleTime = (currentTime.getTime() - openedAt.getTime()) / 1000;
650                                        if (idleTime >= idleTimeout && connectionPool.contains(hashCode)) {
651                                                if (!abortList.contains(hashCode))
652                                                        abortList.add(hashCode);
653                                        }
654                                }
655                        }
656                        for (Integer hashCode : abortList) {
657                                if (connectionPool.contains(hashCode)) {
658                                        int loadPoolSize = connectionPool.size();
659                                        boolean overMinPoolSize = loadPoolSize > minPoolSize;
660                                        log.debug("Check abort idle connection, overMinPoolSize[%s] = loadPoolSize[%s] > minPoolSize[%s].",overMinPoolSize, loadPoolSize,minPoolSize);
661                                        if (overMinPoolSize && connectionPool.contains(hashCode)) {
662                                                countAbortedIdle++;
663                                                log.mark("AbortIdleTimeoutConnection '{}', ConnectionPoolSize '{}', OpenConnectionSize '{}'.",hashCode,connectionPool.size(),getOpenConnectionSize());
664                                                abort(hashCode);
665                                        }
666                                }
667                        }
668                }
669        }
670
671        /**
672         * Abort invalid connection
673         * Skip execute when 'testValidTimeout' is 0
674         * */
675        private synchronized void abortInvalidConnection() {
676                if (testValidTimeout > 0) {
677                        try {
678                                List<Integer> abortList = new ArrayList<Integer>();
679                                Map<Integer,Date> idleBeginMark = connectionPool.getConnectionIdleBeginMark();
680                                Set<Integer> hashCodeList = idleBeginMark.keySet();
681                                for (Integer hashCode : hashCodeList) {
682                                        Connection conn = connectionPool.getConnectionMark().get(hashCode);
683                                        if (conn != null) {
684                                                boolean valid = conn.isValid(testValidTimeout.intValue());
685                                                if (!valid && !abortList.contains(hashCode))
686                                                        abortList.add(hashCode);
687                                        }
688                                }
689                                for (Integer hashCode : abortList) {
690                                        if (connectionPool.contains(hashCode)) {
691                                                countAbortedInvalid++;
692                                                log.mark("AbortInvalidConnection '{}', ConnectionPoolSize '{}', OpenConnectionSize '{}'.",hashCode,connectionPool.size(),getOpenConnectionSize());
693                                                abort(hashCode);
694                                        }
695                                }
696                        } catch (Exception e) {
697                                log.warn(e.getMessage(), e);
698
699                        }
700                }
701        }
702
703        /**
704         * Create connection to pool
705         * When,
706         *   (loadPoolSize + usedPoolSize) < maxPoolSize
707         *   and
708         *   loadPoolSize < minPoolSize
709         * */
710        private synchronized void createConnectionToPool() {
711                final String threadName = String.format("%s-createConnectionToPool", this.name);
712
713                boolean lessThanMaxPoolSize = getOpenConnectionSize() < maxPoolSize;
714                boolean lessThanMinPoolSize = connectionPool.size() < minPoolSize;
715                boolean lessThanMaxUsage = getUsage() < 1.0D;
716
717                if (log.isDebugEnabled()) {
718                        log.debug("boolean lessThanMaxPoolSize[{}] = getOpenConnectionSize()[{}] < maxPoolSize[{}];",
719                                        lessThanMaxPoolSize, getOpenConnectionSize(), maxPoolSize);
720                        log.debug("boolean lessThanMinPoolSize[{}] = connectionPool.size()[{}] < minPoolSize[{}];",
721                                        lessThanMinPoolSize, connectionPool.size(), minPoolSize);
722                        log.debug("boolean lessThanMaxUsage[{}] = getUsage()[{}] < 1.0D;", lessThanMaxUsage, getUsage());
723                        log.debug("Checking create conditions,LessThanMaxPoolSize={}, LessThanMinPoolSize={}, lessThanMaxUsage={}",
724                                        lessThanMaxPoolSize, lessThanMinPoolSize, lessThanMaxUsage);
725
726                }
727                if (lessThanMaxPoolSize && lessThanMinPoolSize && lessThanMaxUsage) {
728                        int willCreateConnTotal = minPoolSize - connectionPool.size();
729                        if (willCreateConnTotal > 0) {
730                                log.debug("Will create connection total = {}", willCreateConnTotal);
731                        }
732                        for (int i = 0; i < willCreateConnTotal; i++) {
733                                Callable callable = new Callable<Boolean>() {
734                                        @Override
735                                        public Boolean call() {
736                                                try {
737                                                    Thread.currentThread().setName("call-" + threadName);
738                                                        if (closed || Thread.currentThread().isInterrupted()){
739                                                                return false;
740                                                        }
741
742                                                        boolean lessThanMaxPoolSize = getOpenConnectionSize() < maxPoolSize;
743                                                        boolean lessThanMinPoolSize = connectionPool.size() < minPoolSize;
744                                                        boolean lessThanMaxUsage = getUsage() < 1.0D;
745
746                                                        if (lessThanMaxPoolSize && lessThanMinPoolSize && lessThanMaxUsage) {
747                                                                Thread.currentThread().setName(threadName);
748                                                                loadConnectionToPool();
749                                                                log.debug("Loaded connection to pool");
750                                                        }
751                                                } catch (Exception e) {
752                                                        log.warn(e.getMessage(), e);
753                                                }
754                                                return true;
755                                        }
756                                };
757                                connectionPoolExecutor.submit(callable);
758                        }
759                        log.debug("Creating {} connection to pool.", willCreateConnTotal);
760                }
761        }
762
763        /**
764         * Set internal config
765         * @param _configProperties
766         * */
767        private synchronized void setInternalConfig(ConfigProperties _configProperties) {
768                this.configProperties = _configProperties;
769                if (this.configProperties.containsKey("EnableStatusLog"))
770                        setEnableStatusLog(this.configProperties.getBoolean("EnableStatusLog", false));
771
772                if (this.configProperties.containsKey("EnableFullStatusLog"))
773                        setEnableFullStatusLog(this.configProperties.getBoolean("EnableFullStatusLog", true));
774
775                if (this.configProperties.containsKey("StatusLogArchiveDays"))
776                        setStatusLogArchiveDays(this.configProperties.getInteger("StatusLogArchiveDays", 30));
777
778                if (this.configProperties.containsKey("StatusLogFolder"))
779                        setStatusLogFolder(this.configProperties.getString("StatusLogFolder", null));
780
781                if (this.configProperties.containsKey("DynamicReloadConfig"))
782                        setDynamicReloadConfig(this.configProperties.getBoolean("DynamicReloadConfig", false));
783
784                if (this.configProperties.containsKey("MaxPoolSize"))
785                        setMaxPoolSize(this.configProperties.getInteger("MaxPoolSize", Integer.MAX_VALUE));
786
787                if (this.configProperties.containsKey("MinPoolSize"))
788                        setMinPoolSize(this.configProperties.getInteger("MinPoolSize", 0));
789
790                if (this.configProperties.containsKey("LifeCycle"))
791                        setLifeCycle(this.configProperties.getSeconds("LifeCycle", Long.MAX_VALUE));
792
793                if (this.configProperties.containsKey("IdleTimeout"))
794                        setIdleTimeout(this.configProperties.getSeconds("IdleTimeout", 0L));
795
796                if (this.configProperties.containsKey("Timeout"))
797                        setTimeout(this.configProperties.getSeconds("Timeout", 0L));
798
799                if (this.configProperties.containsKey("TransactionTimeout"))
800                        setTransactionTimeout(this.configProperties.getSeconds("TransactionTimeout", 0L));
801
802                if (this.configProperties.containsKey("TestValidTimeout"))
803                        setTestValidTimeout(this.configProperties.getSeconds("TestValidTimeout", 5L));
804
805                if (this.configProperties.containsKey("RefreshPoolTimer"))
806                        setRefreshPoolTimer(this.configProperties.getMilliSeconds("RefreshPoolTimer", 1000L));
807
808                if (this.configProperties.containsKey("DriverJar"))
809                        setDriverJar(this.configProperties.getString("DriverJar", null));
810
811                if (this.configProperties.containsKey("ConnProps"))
812                        setConnProps(this.configProperties.getString("ConnProps", null));
813
814                if (this.configProperties.containsKey("ThreadName"))
815                        setName(this.configProperties.getString("ThreadName", null));
816
817                if (this.configProperties.containsKey("Driver"))
818                        setDriverClassName(this.configProperties.getString("Driver", null));
819
820                if (this.configProperties.containsKey("Url"))
821                        setUrl(this.configProperties.getString("Url", null));
822
823                if (this.configProperties.containsKey("Url[0]"))
824                        setUrl(this.configProperties.getArray("Url"));
825
826                if (this.configProperties.containsKey("Username"))
827                        setUsername(this.configProperties.getString("Username", null));
828
829                if (this.configProperties.containsKey("Password"))
830                        setPassword(this.configProperties.getString("Password", null));
831
832                if (this.configProperties.containsKey("AbortMode"))
833                        setAbortMode(this.configProperties.getString("AbortMode", "abort"));
834
835                if (this.configProperties.containsKey("MaxAveUsageSize"))
836                        setMaxAveUsageSize(this.configProperties.getInteger("MaxAveUsageSize", 10000));
837                        
838                loadedConfig = true;
839        }
840
841        /**
842         * Set internal config
843         * @param configPropertiesContent
844         * */
845        private synchronized void setInternalConfig(String configPropertiesContent) throws IOException {
846                log.debug("setInternalConfig -> {}", configPropertiesContent);
847                ConfigProperties _configProperties = new ConfigProperties();
848                _configProperties.load(new StringReader(configPropertiesContent));
849                setInternalConfig(_configProperties);
850        }
851
852        /**
853         * Set config
854         * @param configPropertiesContent
855         * */
856        public synchronized void setConfig(String configPropertiesContent) throws IOException {
857                setInternalConfig(configPropertiesContent);
858                setDynamicReloadConfig(false);
859        }
860
861        /**
862         * Set config
863         * @param _configProperties
864         * */
865        public synchronized void setConfig(ConfigProperties _configProperties) throws IOException {
866                setInternalConfig(_configProperties);
867                setDynamicReloadConfig(false);
868        }
869
870        /**
871         * Reload config
872         * When 'appDataSource' is file object
873         * */
874        private synchronized void reloadConfig() {
875                try {
876                        if (this.appDataSource != null) {
877                            ConfigProperties loadedConfigProperties = this.configProperties;
878                                String absPath = this.appDataSource.getAbsolutePath();
879                                Logger.systemDebug(DriverDataSource.class, "Loading config(For System Init Debug) -> {}", absPath);
880                                log.debug("Loading config -> {}", absPath);
881                                String dsCnfText = FileTools.text(absPath);
882                                if (dsCnfText != null) {
883                                        setInternalConfig(dsCnfText);
884                                }
885                                boolean same = ConfigProperties.compareProperties(loadedConfigProperties,this.configProperties);
886                                if(!same){
887                                    Logger.systemMark(DriverDataSource.class, "DriverDataSource '{}' Reloaded", name);
888                                }
889                        }
890                } catch (Exception e) {
891                        log.warn(e);
892                }
893        }
894
895        /**
896         *  Archive log
897         * */
898        private void archiveLog() {
899                if (statusLogFile != null && statusLogArchiveDays != null) {
900                        File statusLogFileParent = statusLogFile.getParentFile();
901                        if (statusLogArchiveDays > 0 && statusLogFileParent != null) {
902                                try {
903                                        long fileOutArchiveDaysMs = new Date().getTime() - (statusLogArchiveDays * 24 * 3600000L);
904                                        deleteFilesOlderThan(statusLogFileParent, fileOutArchiveDaysMs);
905                                } catch (Exception e) {
906                                        log.warn(e);
907
908                                }
909                        }
910                }
911        }
912
913        /**
914         * Delete old archive log
915         * */
916        private void deleteFilesOlderThan(File directory, long fileOutArchiveDaysMs) throws IOException {
917                if (directory.isDirectory()) {
918                        File[] files = directory.listFiles();
919                        if (files != null) {
920                                for (File file : files) {
921                                        if (file.isFile()) {
922                                                boolean isLogFile = file.getName().toLowerCase().endsWith(".log");
923                                                if (isLogFile) {
924                                                        boolean canWrite = file.canWrite();
925                                                        if (canWrite) {
926                                                                long lastModified = file.lastModified();
927                                                                if (lastModified < fileOutArchiveDaysMs) {
928                                                                        Files.deleteIfExists(Paths.get(file.toURI()));
929                                                                }
930                                                        }
931                                                }
932                                        }
933                                }
934                        }
935                }
936        }
937
938        /**
939         * Write connection status log
940         * */
941        private synchronized void statusLogOutput() {
942                File folder = statusLogFile.getParentFile();
943                if (folder.exists()) {
944                        if (!folder.canWrite())
945                                return;
946                } else {
947                        folder.mkdirs();
948                }
949                if (statusLogFile.exists()) {
950                        if (!statusLogFile.canWrite())
951                                return;
952                }
953
954                SimpleDateFormat timeFormat = new SimpleDateFormat("yyyyMMdd HH:mm:ss.SSS");
955                Map<Integer, String> CONNECTION_THREAD_MARK_SYNC = connectionPool.getConnectionThreadMark();
956                Map<Integer, Date> CONNECTION_OPENED_AT_MARK_SYNC = connectionPool.getConnectionOpenedAtMark();
957                Map<Integer, Date> CONNECTION_USING_AT_MARK_SYNC = connectionPool.getConnectionUsingAtMark();
958                Map<Integer, Date> CONNECTION_IDLE_BEGIN_MARK_SYNC = connectionPool.getConnectionIdleBeginMark();
959
960                int usingSize = CONNECTION_USING_AT_MARK_SYNC.keySet().size();
961                int idleSize = CONNECTION_IDLE_BEGIN_MARK_SYNC.keySet().size();
962
963                boolean isFull = (usingSize > idleSize && idleSize == 0 && usingSize >= maxPoolSize);
964
965                String driverInfo = String.format("Driver: %s%s", this.driverClassName, System.lineSeparator());
966                StringBuffer countInfoSb = new StringBuffer();
967                countInfoSb.append(
968                                String.format("MinPoolSize: %s\tMaxPoolSize: %s%s", minPoolSize, maxPoolSize, System.lineSeparator()));
969                countInfoSb.append(String.format("RefreshPoolTimer: %sms%s", refreshPoolTimer, System.lineSeparator()));
970                countInfoSb.append(String.format("TestValidTimeout: %ss\tIdleTimeout: %ss%s", testValidTimeout, idleTimeout,
971                                System.lineSeparator()));
972                countInfoSb.append(String.format("Timeout: %ss\tTransactionTimeout: %ss%s", timeout, transactionTimeout,
973                                System.lineSeparator()));
974                countInfoSb.append(
975                                String.format("LifeCycle: %ss\tCountClosed: %s%s", lifeCycle, countClosed, System.lineSeparator()));
976                countInfoSb.append(String.format("CountCreatedConnection: %s\tCountAbortedCloseFailed: %s%s",
977                                countCreatedConnection, countAbortedCloseFailed, System.lineSeparator()));
978                countInfoSb.append(String.format("CountAbortedIdleTimeouted: %s\tCountAbortedTimeouted: %s%s", countAbortedIdle,
979                                countAbortedTimeout, System.lineSeparator()));
980                countInfoSb.append(String.format("CountAbortedLifeCycleEnded: %s\tCountAbortedInvalid: %s%s",
981                                countAbortedLifeCycle, countAbortedInvalid, System.lineSeparator()));
982                countInfoSb.append(String.format("MaxUsage: %.2f%%\tCurrentUsage: %.2f%%\tAveUsage: %.2f%%\tCounter: %s/%s%s", maxUsage * 100,
983                                getUsage() * 100,getAveUsage() * 100, aveUsageSum.size(),maxAveUsageSize,System.lineSeparator()));
984
985                String urlInfo = String.format("URL: %s%s", this.url, System.lineSeparator());
986                String usernameInfo = String.format("User: %s%s", this.username, System.lineSeparator());
987                String header = String.format("%sLine\tHashCode\tCreatedTime\tChangedTime\tStatus\tLastUsingBy%s",
988                                System.lineSeparator(), System.lineSeparator());
989                String lineFormat = "%s\t%s\t%s\t%s\t%s\t%s%s";
990                StringBuffer sbf = new StringBuffer();
991                sbf.append(String.format("ThreadName: %s%s", this.name, System.lineSeparator()));
992                sbf.append(String.format("Host: %s\tPID: %s\tSystemTime: %s%s", CommonTools.getHostname(), PID,
993                                timeFormat.format(new Date()), System.lineSeparator()));
994                sbf.append(urlInfo);
995                sbf.append(usernameInfo);
996                sbf.append(driverInfo);
997                sbf.append(countInfoSb.toString());
998                sbf.append(header);
999                int lineNumber = 1;
1000
1001                Set<Integer> keys = CONNECTION_OPENED_AT_MARK_SYNC.keySet();
1002                List<Object[]> list = new ArrayList<Object[]>();
1003                for (Integer hashCode : keys) {
1004                        String status = "-";
1005                        String thread = "-";
1006                        Date changedTime = null;
1007                        Date createdTime = CONNECTION_OPENED_AT_MARK_SYNC.get(hashCode);
1008
1009                        if (CONNECTION_THREAD_MARK_SYNC.containsKey(hashCode)) {
1010                                thread = CONNECTION_THREAD_MARK_SYNC.get(hashCode);
1011                        }
1012
1013                        if (CONNECTION_USING_AT_MARK_SYNC.containsKey(hashCode)) {
1014                                changedTime = CONNECTION_USING_AT_MARK_SYNC.get(hashCode);
1015                                status = "Using";
1016                        } else if (CONNECTION_IDLE_BEGIN_MARK_SYNC.containsKey(hashCode)) {
1017                                changedTime = CONNECTION_IDLE_BEGIN_MARK_SYNC.get(hashCode);
1018                                status = "Idle";
1019                        }
1020
1021                        Object[] objArr = new Object[] { hashCode, createdTime, changedTime, status, thread };
1022                        list.add(objArr);
1023                }
1024
1025                Comparator comp = new Comparator() {
1026                        @Override
1027                        public int compare(Object arg0, Object arg1) {
1028                                Object[] objArr0 = (Object[]) arg0;
1029                                Object[] objArr1 = (Object[]) arg1;
1030                                Date date0 = (Date) objArr0[1];
1031                                Date date1 = (Date) objArr1[1];
1032                                return date0.getTime() < date1.getTime() ? -1 : 1;
1033                        }
1034                };
1035
1036                Collections.sort(list, comp);
1037
1038                for (Object[] objArr : list) {
1039                        Integer hashCode = (Integer) objArr[0];
1040                        Date createdTime = (Date) objArr[1];
1041                        String createdTimeStr = createdTime == null ? "-" : timeFormat.format(createdTime);
1042                        Date changedTime = (Date) objArr[2];
1043                        String changedTimeStr = changedTime == null ? "-" : timeFormat.format(changedTime);
1044                        String status = objArr[3].toString();
1045                        String thread = objArr[4].toString();
1046                        String line = String.format(lineFormat, lineNumber, hashCode, createdTimeStr, changedTimeStr, status,
1047                                        thread, System.lineSeparator());
1048                        sbf.append(line);
1049                        lineNumber++;
1050                }
1051
1052                try {
1053                        String msg = sbf.toString();
1054                        FileTools.write(statusLogFile, msg, false);
1055
1056                        if (isFull && enableFullStatusLog) {
1057                                SimpleDateFormat timeFullFormat = new SimpleDateFormat("yyyyMMddHHmm");
1058                                File fullStatusLogFile = new File(String.format("%s_full_%s", statusLogFile.getAbsolutePath(),
1059                                                timeFullFormat.format(new Date())));
1060                                FileTools.write(fullStatusLogFile, msg, false);
1061
1062                        }
1063                } catch (IOException e) {
1064                        log.warn(e.getMessage(), e);
1065
1066                }
1067        }
1068
1069        /**
1070         * Manage connection pool
1071         * */
1072        private synchronized void manageConnectionPool() {
1073                if (!inited) {
1074                        inited = true;
1075
1076                        String threadName = this.name;
1077                        ExecutorService executor = Executors.newFixedThreadPool(2);
1078                        executor.execute(new Runnable() {
1079
1080                                @Override
1081                                public void run() {
1082                                        Thread.currentThread().setName(String.format("%s-manageConnectionPool-1", threadName));
1083                                        while (true) {
1084                                                if (closed || Thread.currentThread().isInterrupted())
1085                                                        break;
1086
1087                                                try {
1088                                                        abortLifeCycleEndedConnection();
1089                                                        abortTimeoutConnection();
1090                                                        abortIdleTimeoutConnection();
1091                                                        createConnectionToPool();
1092                                                        countUsage(true);
1093                                                        if (enableStatusLog && statusLogFile != null) {
1094                                                                statusLogOutput();
1095                                                                archiveLog();
1096                                                        }
1097                                                        if (dynamicReloadConfig) {
1098                                                            boolean overTime = (System.currentTimeMillis() - dynamicReloadConfigTime) >= RELOAD_TIMER;
1099                                                            if(overTime) {
1100                                                                reloadConfig();
1101                                                                dynamicReloadConfigTime = System.currentTimeMillis();
1102                                                            }
1103                                                        }
1104                                                } catch (Exception e) {
1105                                                        log.warn(e.getMessage(), e);
1106                                                }
1107
1108                                                try {
1109                                                        Thread.sleep(refreshPoolTimer);
1110                                                } catch (InterruptedException e) {
1111                                                        log.warn(e.getMessage(), e);
1112                                                }
1113                                        }
1114                                }
1115
1116                        });
1117
1118                        executor.execute(new Runnable() {
1119
1120                                @Override
1121                                public void run() {
1122                                        Thread.currentThread().setName(String.format("%s-manageConnectionPool-2", threadName));
1123                                        while (true) {
1124                                                if (closed || Thread.currentThread().isInterrupted())
1125                                                        break;
1126
1127                                                try {
1128                                                        abortInvalidConnection();
1129                                                } catch (Exception e) {
1130                                                        log.warn(e.getMessage(), e);
1131
1132                                                }
1133
1134                                                try {
1135                                                        Thread.sleep(refreshPoolTimer);
1136                                                } catch (InterruptedException e) {
1137                                                        log.warn(e.getMessage(), e);
1138                                                }
1139                                        }
1140                                }
1141
1142                        });
1143                }
1144        }
1145
1146        /**
1147         * Get connection
1148         * @exception SQLException
1149         * @return Connection
1150         * */
1151    @Override
1152        public synchronized Connection getConnection() throws SQLException {
1153            try{
1154                manageConnectionPool();
1155                Date currentTime = new Date();
1156                Connection conn = null;
1157                boolean allowedGetSingle = true;
1158
1159                if(connectionPool.size() == 0){
1160                     loadConnectionToPool();
1161                }
1162
1163                conn = connectionPool.getConnection();
1164                if (conn == null) {
1165                        throwConnIsNull();
1166                }                       
1167            countUsage(false);
1168                
1169                if (conn == null) {
1170                        throwConnIsNull();
1171                }
1172                log.debug("Get Connection -> {}", conn.hashCode());
1173                return new DriverConnection(this, conn);
1174            }catch(Exception e){
1175                log.error(e.getMessage(),e);
1176                throwConnIsNull();
1177            }
1178            return null;
1179        }
1180        
1181        private void throwConnIsNull() throws SQLException {
1182        int errorCode = 99904;
1183                String errorMsg = "Connection is null.";
1184                log.warn(String.format("ERROR CODE: (%s) %s", errorCode, errorMsg));
1185                throw new SQLException(errorMsg) {
1186                        @Override
1187                        public int getErrorCode() {
1188                                return errorCode;
1189                        }
1190                };
1191        }
1192
1193        private synchronized void checkConnectionPoolSize() throws SQLException {
1194                int openConnectionSize = getOpenConnectionSize();
1195                if (openConnectionSize >= maxPoolSize) {
1196                        int errorCode = 99903;
1197                        String errorMsg = String.format("Full connection pool,current size is %s.", openConnectionSize);
1198                        log.warn(String.format("ERROR CODE: (%s) %s", errorCode, errorMsg));
1199                        throw new SQLException(errorMsg) {
1200                                @Override
1201                                public int getErrorCode() {
1202                                        return errorCode;
1203                                }
1204                        };
1205
1206                }
1207        }
1208
1209        /**
1210         * Get connection properties
1211         * @return Properties
1212         * */
1213        private Properties getConnectionProperties() {
1214                Properties props = new Properties();
1215                props.setProperty("user", username);
1216                props.setProperty("password", password);
1217
1218                if (!CommonTools.isBlank(connProps)) {
1219                        try {
1220                                String[] ps = connProps.split(";");
1221                                int psSize = ps.length;
1222                                for (int i = 0; i < psSize; i++) {
1223                                        String[] propKeyValue = ps[i].split("=");
1224                                        String pkey = propKeyValue[0].trim();
1225                                        String pval = propKeyValue[1].trim();
1226                                        props.setProperty(pkey, pval);
1227                                }
1228                        } catch (Exception e) {
1229                                log.warn(e.getMessage(), e);
1230
1231                        }
1232                }
1233                return props;
1234        }
1235
1236        /**
1237         * Get connection from driver
1238         * @exception SQLException
1239         * @return Connection
1240         * */
1241        private synchronized Connection getConnectionFromDriver() throws SQLException {
1242                Properties props = getConnectionProperties();
1243                Connection conn = null;
1244                if (driverJar == null) {
1245                        conn = tryGetConnection(null, props);
1246                } else {
1247                        Driver driver = loadJdbcDriver(driverJar, driverClassName);
1248                        log.debug("Success init drive -> {}", driverClassName);
1249                        conn = tryGetConnection(driver, props);
1250                }
1251
1252                if (conn == null) {
1253                        throwConnIsNull();
1254                }
1255                return conn;
1256        }
1257
1258        /**
1259         * Load connection to pool
1260         * @exception SQLException
1261         * */
1262        private synchronized void loadConnectionToPool() throws SQLException {
1263                checkConnectionPoolSize();
1264                log.debug("Opening(Pool)");
1265                final Connection conn = getConnectionFromDriver();
1266                final Integer hashCode = conn.hashCode();
1267                log.debug("Created Connection -> {}", hashCode);
1268                connectionPool.add(conn);
1269        }
1270 
1271        /**
1272         * Close connection
1273         * @exception SQLException 
1274         * @param conn - Connection object
1275         * */
1276        protected void closeConnection(Connection conn) throws SQLException {
1277                if (conn != null)
1278                        closeConnection(conn.hashCode());
1279        }
1280
1281        /**
1282         * Close connection 
1283         * @exception SQLException
1284         * @param hashCode - Connection hash code
1285         * */
1286        protected void closeConnection(Integer hashCode) throws SQLException {
1287                Connection conn = connectionPool.getConnectionMark().get(hashCode);
1288                if (conn != null) {
1289                        try {
1290                                synchronized (this) {
1291                                        connectionPool.removeConnectionUsingAtMark(hashCode);
1292                                }
1293                                if (lifeCycle <= 0) {
1294                                        conn.close();
1295                                        connectionPool.removeAll(hashCode);
1296                                        countClosed++;
1297                                        log.debug("Closed Connection -> {}", hashCode);
1298                                } else {
1299                                    if(closed){
1300                                        conn.close();
1301                                                connectionPool.removeAll(hashCode);
1302                                                countClosed++;
1303                                        log.debug("Closed Connection -> {}", hashCode);
1304                                    }else{
1305                                        conn.setAutoCommit(true);
1306                                        connectionPool.freed(hashCode);
1307                                        log.debug("Freed Connection -> {}", hashCode);
1308                                    }
1309                                }
1310                        } catch (SQLException e) {
1311                                addCountAbortedCloseFailed(+1);
1312                                abort(hashCode);
1313
1314                                throw e;
1315                        }
1316                }
1317        }
1318
1319        /**
1320         * Add count abort close failed for write status log
1321         * @param value - Change value
1322         * */
1323        private synchronized void addCountAbortedCloseFailed(int value) {
1324                countAbortedCloseFailed += value;
1325        }
1326
1327        /**
1328         * Close all connections
1329         * */
1330        public synchronized void close() {
1331                closed = true;
1332                log.mark("Try Abort(On SystemExit,CLOSE-{}) connections.",name);
1333                Map<Integer,Connection> connectionMark = connectionPool.getConnectionMark();
1334                Iterator<Integer> itr = connectionMark.keySet().iterator();
1335                while (itr.hasNext()) {
1336                        Integer key = itr.next();
1337                        Connection conn = connectionMark.get(key);
1338                        if (conn != null) {
1339                                log.mark("Abort(On SystemExit) connection '{}.{}'.",name,conn.hashCode());
1340                                abort(conn.hashCode());
1341                        }
1342                }
1343                List<Integer> connList = connectionPool.list();
1344                for(Integer hashCode : connList){
1345                    log.mark("Abort(On SystemExit) connection '{}.{}'.",name,hashCode);
1346                    abort(hashCode);
1347                }               
1348                log.mark("Aborted all connections '{}'.",name);     
1349        }
1350        
1351        /**
1352         * Loop close all connections
1353         * */
1354        public synchronized void closeAll() {
1355                closed = true;
1356                log.mark("Try Abort(On SystemExit,CLOSE_ALL-{}) connections.",name);
1357                List<Integer> connList = connectionPool.list();
1358                for(Integer hashCode : connList){
1359                    log.mark("Abort(On SystemExit) connection '{}.{}'.",name,hashCode);
1360                    abort(hashCode);
1361                }
1362                double currentUsage = getUsage();
1363                log.mark("Checking usage for close all , the '{}' usage is '{}'.",name,currentUsage);
1364                if(currentUsage > 0){
1365                    try{
1366                        Thread.sleep(1000L);
1367                    }catch(InterruptedException e){
1368                        log.error(e.getMessage(),e);
1369                    }
1370                    closeAll();
1371                }
1372                log.mark("Aborted all connections '{}'.",name);
1373        }       
1374        
1375        /**
1376         * Deregister JDBC Driver
1377         * For system exit
1378         * */
1379        public synchronized static void deregisterDriver(){
1380            LoggerFactory.getLogger(DriverDataSource.class).mark("DeregisterDriver processing.");
1381            Enumeration<Driver> drivers = DriverManager.getDrivers();
1382            if(drivers != null){
1383                while (drivers.hasMoreElements()) {
1384                        Driver driver = drivers.nextElement();
1385                        try{
1386                            LoggerFactory.getLogger(DriverDataSource.class).mark("Driver: '{}'",driver.hashCode());
1387                            DriverManager.deregisterDriver(driver);
1388                        }catch(Exception e){
1389                            LoggerFactory.getLogger(DriverDataSource.class).error(e.getMessage(),e);
1390                        }
1391                }
1392            }
1393                LoggerFactory.getLogger(DriverDataSource.class).mark("DeregisterDriver processed.");
1394        }
1395
1396        /**
1397         * Try get connection 
1398         * Support multi url
1399         * @param driver - Driver
1400         * @param props - Connection properties ,var name is 'connProps'
1401         * */
1402        private synchronized Connection tryGetConnection(Driver driver, Properties props) {
1403                Connection conn = null;
1404                int urlLastIndex = urlList.size() - 1;
1405
1406                do {
1407                        if (closed)
1408                                break;
1409
1410                        if (connectionPool.size() >= maxPoolSize)
1411                                break;
1412
1413                        if (!CONN_ERROR_TIME.containsKey(urlIndex)) {
1414                                CONN_ERROR_TIME.put(urlIndex, 0L);
1415                        }
1416
1417                        Long connErrorTime = CONN_ERROR_TIME.get(urlIndex);
1418                        if (connErrorTime > 0) {
1419                                ConfigProperties cp = getConfigProperties();
1420                                long connCheckMs = 10000L;
1421                                if (cp != null) {
1422                                        connCheckMs = cp.getLong("TryGetConnectionCheckTime", 10000L);
1423                                }
1424                                boolean isSkipConn = connErrorTime > 0 && (System.currentTimeMillis() - connErrorTime) <= connCheckMs;
1425                                if (isSkipConn) {
1426                                        try {
1427                                                Thread.sleep(100L);
1428                                        } catch (InterruptedException e) {
1429                                                log.debug(e.getMessage(), e);
1430                                        }
1431                                        continue;
1432                                }
1433                        }
1434
1435                        url = urlList.get(urlIndex);
1436                        try {
1437                                if (driver == null) {
1438                                        Class.forName(driverClassName);
1439                                        conn = DriverManager.getConnection(url, props);
1440                                } else {
1441                                        conn = driver.connect(url, props);
1442                                }
1443                                conn.setAutoCommit(true);
1444
1445                                countCreatedConnection++;
1446
1447                                CONN_ERROR_TIME.put(urlIndex, 0L);
1448
1449                                break;
1450                        } catch (Exception e) {
1451
1452                                CONN_ERROR_TIME.put(urlIndex, System.currentTimeMillis());
1453
1454                                urlIndex += 1;
1455
1456                                Logger.systemError(DriverDataSource.class,"Failed connect to '{}'.", url);
1457                                Logger.systemError(DriverDataSource.class,e.getMessage(), e);
1458
1459                        }
1460                } while (urlIndex <= urlLastIndex);
1461
1462                if (conn == null) {
1463                        urlIndex = 0;
1464                        url = urlList.get(urlIndex);
1465                }
1466
1467                return conn;
1468        }
1469
1470        /**
1471         * Abort connection by hash code
1472         * @param hashCode - Connection hash code
1473         * */
1474        protected void abort(Integer hashCode) {
1475                log.debug("Abort Connection {}", hashCode);
1476                Connection conn = connectionPool.getConnectionMark().get(hashCode);
1477                if (conn != null) {
1478                        try {
1479                                callAbortConn(conn);
1480                                log.debug("Aborted Connection -> {}", hashCode);
1481                                connectionPool.removeAll(hashCode);
1482                                log.debug("Abort(If Aborted,{}), UsedPoolSize={}.", hashCode, getOpenConnectionSize()); 
1483                        } catch (Exception e) {
1484                                log.warn(e.getMessage(), e);
1485
1486                        }
1487                }
1488        }
1489
1490        /**
1491         * Call abort connection 
1492         * Please see 'abortMode' options (rollback,close,abort)
1493         * Default is 'abort'
1494         * @param conn - Connection
1495         * */
1496        private void callAbortConn(Connection conn) {
1497                try {
1498                        if (abortMode.equalsIgnoreCase("rollback")) {
1499                                if (!conn.getAutoCommit())
1500                                        conn.rollback();
1501
1502                                conn.close();
1503                        } else if (abortMode.equalsIgnoreCase("close")) {
1504                                conn.close();
1505                        } else {
1506                                conn.abort(Executors.newFixedThreadPool(1));
1507                        }
1508                } catch (SQLException e) {
1509                        log.warn("EROR CODE: {}", e.getErrorCode());
1510                        log.warn(e.getMessage(), e);
1511
1512                } catch (Exception e) {
1513                        log.warn(e.getMessage(), e);
1514
1515                }
1516        }
1517
1518        /**
1519         * Load jdbc driver jar
1520         * @param driverJar - It is jar file path or jar dir path
1521         * @param driver
1522         * @exception SQLException
1523         * @return Driver
1524         * */
1525        private synchronized Driver loadJdbcDriver(String driverJar, String driver) throws SQLException {
1526                try {
1527
1528                        if (driverJar == null) {
1529                                int errorCode = 99901;
1530                                String errorMsg = "Driver jar is null.";
1531                                log.warn(String.format("ERROR CODE: (%s) %s", errorCode, errorMsg));
1532                                throw new SQLException(errorMsg) {
1533                                        @Override
1534                                        public int getErrorCode() {
1535                                                return errorCode;
1536                                        }
1537                                };
1538                        }
1539
1540                        Driver driverNewInstance = DRIVER_MARK.get(driverJar);
1541
1542                        if (driverNewInstance == null) {
1543                                URLClassLoader driverClassLoader = CommonTools.loadJars(driverJar);
1544                                Class classToLoad = Class.forName(driver, true, driverClassLoader);
1545                                Constructor driverConstructor = classToLoad.getConstructor();
1546                                driverConstructor.setAccessible(true);
1547                                driverNewInstance = (Driver) driverConstructor.newInstance();
1548
1549                                DRIVER_MARK.put(driverJar, driverNewInstance);
1550                        }
1551
1552                        return driverNewInstance;
1553                } catch (Exception e) {
1554                        int errorCode = 99902;
1555                        String errorMsg = e.getMessage();
1556                        log.warn(String.format("ERROR CODE: (%s) %s", errorCode, errorMsg));
1557                        throw new SQLException(e) {
1558
1559                                @Override
1560                                public int getErrorCode() {
1561                                        return errorCode;
1562                                }
1563
1564                        };
1565                }
1566        }
1567
1568        /**
1569         * Get owner thread name
1570         * For use sync DataSource
1571         * @return String
1572         * */
1573        public String getOwner() {
1574                return owner;
1575        }
1576
1577        /**
1578         * Get config properties
1579         * @return ConfigProperties
1580         * */
1581        public ConfigProperties getConfigProperties() {
1582                return configProperties;
1583        }
1584
1585        /**
1586         * Get sync data source list
1587         * @exception SQLException
1588         * @param owner - From master data source thread name
1589         * @return List<DriverDataSource>
1590         * */
1591        protected final synchronized List<DriverDataSource> getSyncDataSourceList(String owner) throws SQLException {
1592                if (syncDriverDataSource == null) {
1593                        syncDriverDataSource = new ArrayList<DriverDataSource>();
1594                        List<String> syncDsPropList = configProperties.getArray("SyncDataSource");
1595
1596                        if (syncDsPropList == null)
1597                                return null;
1598
1599                        for (String syncDsProp : syncDsPropList) {
1600
1601                                if (CommonTools.isBlank(syncDsProp))
1602                                        continue;
1603
1604                                File _appDsFile = new File(syncDsProp);
1605                                if (_appDsFile.exists()) {
1606                                        DriverDataSource dds = new DriverDataSource(_appDsFile);
1607                                        dds.owner = owner;
1608                                        syncDriverDataSource.add(dds);
1609                                } else {
1610                                        int errorCode = 99905;
1611                                        String errorMsg = String.format("SyncDatasource is null - %s .", syncDsProp);
1612                                        throw new SQLException(errorMsg) {
1613                                                @Override
1614                                                public int getErrorCode() {
1615                                                        return errorCode;
1616                                                }
1617                                        };
1618                                }
1619
1620                        }
1621                }
1622                return syncDriverDataSource;
1623        }
1624
1625        /**
1626         * For checksum master and sync data source connection url
1627         * Pause sync where same connection url
1628         * @return String
1629         * */
1630        public String getFingerprint() {
1631                return CommonTools.md5(String.format("%s-%s", this.url, this.username));
1632        }
1633        
1634        public synchronized double getAveUsage(){
1635            int size = aveUsageSum.size();
1636            double sum = 0.0D;
1637            for(int i = 0; i < size;i++){
1638                sum += aveUsageSum.get(i);
1639            }
1640            
1641            if(size <= 0) return 0.0D;
1642            
1643            return sum/size;
1644        }
1645        
1646        public void setMaxAveUsageSize(Integer maxAveUsageSize){
1647            this.maxAveUsageSize = maxAveUsageSize == null ? 10000 : maxAveUsageSize;
1648        }
1649
1650        private synchronized void countUsage(boolean calAve) {
1651                double usage = getUsage();
1652        if(calAve){
1653            if(aveUsageSum.size() >= maxAveUsageSize){
1654                        aveUsageSum.remove(0);
1655            }
1656            aveUsageSum.add(usage);
1657        }
1658                if (usage > maxUsage) {
1659                        maxUsage = usage;
1660                }
1661        }
1662
1663        /**
1664         * Get pool using 
1665         * @return double
1666         * */
1667        public synchronized double getUsage() {
1668                Map<Integer, Date> CONNECTION_USING_AT_MARK_SYNC = connectionPool.getConnectionUsingAtMark();
1669                int usingSize = CONNECTION_USING_AT_MARK_SYNC.keySet().size();
1670
1671                if (usingSize <= 0)
1672                        return 0.0D;
1673
1674                if (maxPoolSize <= 0)
1675                        return 0.0D;
1676                        
1677                return usingSize / (maxPoolSize * 1.0D);
1678        }
1679
1680        /** Abandon the following methods **/
1681
1682        @Override
1683        public java.lang.Object unwrap(java.lang.Class arg0) throws java.sql.SQLException {
1684                return null;
1685        }
1686
1687        @Override
1688        public boolean isWrapperFor(java.lang.Class arg0) throws java.sql.SQLException {
1689                return false;
1690        }
1691
1692        @Override
1693        public java.util.logging.Logger getParentLogger() throws java.sql.SQLFeatureNotSupportedException {
1694                return null;
1695        }
1696
1697        @Override
1698        public void setLogWriter(PrintWriter arg0) throws SQLException {
1699        }
1700
1701        @Override
1702        public PrintWriter getLogWriter() throws SQLException {
1703                return null;
1704        }
1705
1706        @Override
1707        public int getLoginTimeout() throws SQLException {
1708                return 0;
1709        }
1710
1711        @Override
1712        public void setLoginTimeout(int arg0) throws SQLException {
1713        }
1714
1715        @Override
1716        public Connection getConnection(String arg0, String arg1) throws SQLException {
1717                return null;
1718        }
1719
1720        @Override
1721        public ConnectionBuilder createConnectionBuilder() throws SQLException {
1722                return null;
1723        }
1724
1725}