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