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                                                        
1073                                                        if(!closed && !Thread.currentThread().isInterrupted()){
1074                                                            createConnectionToPool();
1075                                                        }
1076                                                        
1077                                                        countUsage(true);
1078                                                        if (enableStatusLog && statusLogFile != null) {
1079                                                                statusLogOutput();
1080                                                                archiveLog();
1081                                                        }
1082                                                } catch (Exception e) {
1083                                                        log.warn(e.getMessage(), e);
1084                                                }
1085
1086                                                try {
1087                                                        Thread.sleep(refreshPoolTimer);
1088                                                } catch (InterruptedException e) {
1089                                                        log.warn(e.getMessage(), e);
1090                                                }
1091                                        }
1092                                }
1093
1094                        });
1095
1096            executor = Executors.newFixedThreadPool(1);
1097                        executor.execute(new Runnable() {
1098
1099                                @Override
1100                                public void run() {
1101                                        Thread.currentThread().setName(String.format("%s-manageConnectionPool-2", threadName));
1102                                        while (true) {
1103                                                if (closed || Thread.currentThread().isInterrupted())
1104                                                        break;
1105
1106                                                try {
1107                                                        abortInvalidConnection();
1108                                                } catch (Exception e) {
1109                                                        log.warn(e.getMessage(), e);
1110
1111                                                }
1112
1113                                                try {
1114                                                        Thread.sleep(refreshPoolTimer);
1115                                                } catch (InterruptedException e) {
1116                                                        log.warn(e.getMessage(), e);
1117                                                }
1118                                        }
1119                                }
1120
1121                        });
1122                }
1123        }
1124
1125        /**
1126         * Get connection
1127         * @exception SQLException
1128         * @return Connection
1129         * */
1130    @Override
1131        public synchronized Connection getConnection() throws SQLException {
1132            try{
1133                manageConnectionPool();
1134                Date currentTime = new Date();
1135                Connection conn = null;
1136                boolean allowedGetSingle = true;
1137
1138                if(connectionPool.size() == 0){
1139                     loadConnectionToPool();
1140                }
1141
1142                conn = connectionPool.getConnection();
1143                if (conn == null) {
1144                        throwConnIsNull();
1145                }                       
1146            countUsage(false);
1147                
1148                if (conn == null) {
1149                        throwConnIsNull();
1150                }
1151                log.debug("Get Connection -> {}", conn.hashCode());
1152                return new DriverConnection(this, conn);
1153            }catch(Exception e){
1154                log.error(e.getMessage(),e);
1155                throwConnIsNull();
1156            }
1157            return null;
1158        }
1159        
1160        private void throwConnIsNull() throws SQLException {
1161        int errorCode = 99904;
1162                String errorMsg = "Connection is null.";
1163                log.warn(String.format("ERROR CODE: (%s) %s", errorCode, errorMsg));
1164                throw new SQLException(errorMsg) {
1165                        @Override
1166                        public int getErrorCode() {
1167                                return errorCode;
1168                        }
1169                };
1170        }
1171
1172        private synchronized void checkConnectionPoolSize() throws SQLException {
1173                int openConnectionSize = getOpenConnectionSize();
1174                if (openConnectionSize >= maxPoolSize) {
1175                        int errorCode = 99903;
1176                        String errorMsg = String.format("Full connection pool,current size is %s.", openConnectionSize);
1177                        log.warn(String.format("ERROR CODE: (%s) %s", errorCode, errorMsg));
1178                        throw new SQLException(errorMsg) {
1179                                @Override
1180                                public int getErrorCode() {
1181                                        return errorCode;
1182                                }
1183                        };
1184
1185                }
1186        }
1187
1188        /**
1189         * Get connection properties
1190         * @return Properties
1191         * */
1192        private Properties getConnectionProperties() {
1193                Properties props = new Properties();
1194                props.setProperty("user", username);
1195                props.setProperty("password", password);
1196
1197                if (!CommonTools.isBlank(connProps)) {
1198                        try {
1199                                String[] ps = connProps.split(";");
1200                                int psSize = ps.length;
1201                                for (int i = 0; i < psSize; i++) {
1202                                        String[] propKeyValue = ps[i].split("=");
1203                                        String pkey = propKeyValue[0].trim();
1204                                        String pval = propKeyValue[1].trim();
1205                                        props.setProperty(pkey, pval);
1206                                }
1207                        } catch (Exception e) {
1208                                log.warn(e.getMessage(), e);
1209
1210                        }
1211                }
1212                return props;
1213        }
1214
1215        /**
1216         * Get connection from driver
1217         * @exception SQLException
1218         * @return Connection
1219         * */
1220        private synchronized Connection getConnectionFromDriver() throws SQLException {
1221                Properties props = getConnectionProperties();
1222                Connection conn = null;
1223                if (driverJar == null) {
1224                        conn = tryGetConnection(null, props);
1225                } else {
1226                        Driver driver = loadJdbcDriver(driverJar, driverClassName);
1227                        log.debug("Success init drive -> {}", driverClassName);
1228                        conn = tryGetConnection(driver, props);
1229                }
1230
1231                if (conn == null) {
1232                        throwConnIsNull();
1233                }
1234                return conn;
1235        }
1236
1237        /**
1238         * Load connection to pool
1239         * @exception SQLException
1240         * */
1241        private synchronized void loadConnectionToPool() throws SQLException {
1242                checkConnectionPoolSize();
1243                log.debug("Opening(Pool)");
1244                final Connection conn = getConnectionFromDriver();
1245                final Integer hashCode = conn.hashCode();
1246                log.debug("Created Connection -> {}", hashCode);
1247                connectionPool.add(conn);
1248        }
1249 
1250        /**
1251         * Close connection
1252         * @exception SQLException 
1253         * @param conn - Connection object
1254         * */
1255        protected void closeConnection(Connection conn) throws SQLException {
1256                if (conn != null)
1257                        closeConnection(conn.hashCode());
1258        }
1259
1260        /**
1261         * Close connection 
1262         * @exception SQLException
1263         * @param hashCode - Connection hash code
1264         * */
1265        protected void closeConnection(Integer hashCode) throws SQLException {
1266                Connection conn = connectionPool.getConnectionMark().get(hashCode);
1267                if (conn != null) {
1268                        try {
1269                                synchronized (this) {
1270                                        connectionPool.removeConnectionUsingAtMark(hashCode);
1271                                }
1272                                if (lifeCycle <= 0) {
1273                                        conn.close();
1274                                        connectionPool.removeAll(hashCode);
1275                                        countClosed++;
1276                                        log.debug("Closed Connection -> {}", hashCode);
1277                                } else {
1278                                    if(closed){
1279                                        conn.close();
1280                                                connectionPool.removeAll(hashCode);
1281                                                countClosed++;
1282                                        log.debug("Closed Connection -> {}", hashCode);
1283                                    }else{
1284                                        conn.setAutoCommit(true);
1285                                        connectionPool.freed(hashCode);
1286                                        log.debug("Freed Connection -> {}", hashCode);
1287                                    }
1288                                }
1289                        } catch (SQLException e) {
1290                                addCountAbortedCloseFailed(+1);
1291                                abort(hashCode);
1292
1293                                throw e;
1294                        }
1295                }
1296        }
1297
1298        /**
1299         * Add count abort close failed for write status log
1300         * @param value - Change value
1301         * */
1302        private synchronized void addCountAbortedCloseFailed(int value) {
1303                countAbortedCloseFailed += value;
1304        }
1305
1306        /**
1307         * Close all connections
1308         * */
1309        public synchronized void abortAll() {
1310                closed = true;
1311                log.mark("Try Abort(On SystemExit,CLOSE-{}) connections.",name);
1312                Map<Integer,Connection> connectionMark = connectionPool.getConnectionMark();
1313                Iterator<Integer> itr = connectionMark.keySet().iterator();
1314                while (itr.hasNext()) {
1315                        Integer key = itr.next();
1316                        Connection conn = connectionMark.get(key);
1317                        if (conn != null) {
1318                                log.mark("Abort(On SystemExit) connection '{}.{}'.",name,conn.hashCode());
1319                                abort(conn.hashCode());
1320                        }
1321                }
1322                List<Integer> connList = connectionPool.list();
1323                for(Integer hashCode : connList){
1324                    log.mark("Abort(On SystemExit) connection '{}.{}'.",name,hashCode);
1325                    abort(hashCode);
1326                }
1327                log.mark("Aborted all connections '{}'.",name);     
1328        }
1329        
1330        /**
1331         * Loop close all connections
1332         * */
1333        public synchronized void closeAll(int waitSeconds) {
1334                closed = true;
1335                
1336                if(waitSeconds < 0) waitSeconds = 10;
1337                
1338                log.mark("Try Abort(On SystemExit,CLOSE_ALL-{}) connections.",name);
1339                Map<Integer,Date> idleBeginMark = connectionPool.getConnectionIdleBeginMark();
1340                Set<Integer> hashCodeList = idleBeginMark.keySet();
1341                for(Integer hashCode : hashCodeList){
1342                    log.mark("Abort(On SystemExit) connection '{}.{}'.",name,hashCode);
1343                    abort(hashCode);
1344                }
1345                double currentUsage = getUsage();
1346                log.mark("Checking usage for close all , the '{}' usage is '{}'.",name,currentUsage);
1347                
1348                if(currentUsage > 0 && waitSeconds > 0){
1349                  waitSeconds--;
1350                    try{
1351                        Thread.sleep(1000L);
1352                    }catch(InterruptedException e){
1353                        log.error(e.getMessage(),e);
1354                    }
1355                    closeAll(waitSeconds);
1356                }
1357                abortAll();
1358                log.mark("Aborted all connections '{}'.",name);
1359        }       
1360        
1361        /**
1362         * Loop close all connections
1363         * */
1364        public synchronized void closeAll() {
1365                closeAll(10);
1366        }       
1367        
1368        /**
1369         * Deregister JDBC Driver
1370         * For system exit
1371         * */
1372        public synchronized static void deregisterDriver(){
1373            LoggerFactory.getLogger(DriverDataSource.class).mark("DeregisterDriver processing.");
1374            Enumeration<Driver> drivers = DriverManager.getDrivers();
1375            if(drivers != null){
1376                while (drivers.hasMoreElements()) {
1377                        Driver driver = drivers.nextElement();
1378                        try{
1379                            LoggerFactory.getLogger(DriverDataSource.class).mark("Driver: '{}'",driver.hashCode());
1380                            DriverManager.deregisterDriver(driver);
1381                        }catch(Exception e){
1382                            LoggerFactory.getLogger(DriverDataSource.class).error(e.getMessage(),e);
1383                        }
1384                }
1385            }
1386                LoggerFactory.getLogger(DriverDataSource.class).mark("DeregisterDriver processed.");
1387        }
1388
1389        /**
1390         * Try get connection 
1391         * Support multi url
1392         * @param driver - Driver
1393         * @param props - Connection properties ,var name is 'connProps'
1394         * */
1395        private synchronized Connection tryGetConnection(Driver driver, Properties props) {
1396                Connection conn = null;
1397                int urlLastIndex = urlList.size() - 1;
1398
1399                do {
1400                        if (closed || Thread.currentThread().isInterrupted())
1401                                break;
1402
1403                        if (connectionPool.size() >= maxPoolSize)
1404                                break;
1405
1406                        if (!CONN_ERROR_TIME.containsKey(urlIndex)) {
1407                                CONN_ERROR_TIME.put(urlIndex, 0L);
1408                        }
1409
1410                        Long connErrorTime = CONN_ERROR_TIME.get(urlIndex);
1411                        if (connErrorTime > 0) {
1412                                ConfigProperties cp = getConfigProperties();
1413                                long connCheckMs = 10000L;
1414                                if (cp != null) {
1415                                        connCheckMs = cp.getLong("TryGetConnectionCheckTime", 10000L);
1416                                }
1417                                boolean isSkipConn = connErrorTime > 0 && (System.currentTimeMillis() - connErrorTime) <= connCheckMs;
1418                                if (isSkipConn) {
1419                                        try {
1420                                                Thread.sleep(100L);
1421                                        } catch (InterruptedException e) {
1422                                                log.debug(e.getMessage(), e);
1423                                        }
1424                                        continue;
1425                                }
1426                        }
1427
1428                        url = urlList.get(urlIndex);
1429                        try {
1430                                if (driver == null) {
1431                                        Class.forName(driverClassName);
1432                                        conn = DriverManager.getConnection(url, props);
1433                                } else {
1434                                        conn = driver.connect(url, props);
1435                                }
1436                                conn.setAutoCommit(true);
1437
1438                                countCreatedConnection++;
1439
1440                                CONN_ERROR_TIME.put(urlIndex, 0L);
1441
1442                                break;
1443                        } catch (Exception e) {
1444
1445                                CONN_ERROR_TIME.put(urlIndex, System.currentTimeMillis());
1446
1447                                urlIndex += 1;
1448
1449                                Logger.systemError(DriverDataSource.class,"Failed connect to '{}'.", url);
1450                                Logger.systemError(DriverDataSource.class,e.getMessage(), e);
1451
1452                        }
1453                } while (urlIndex <= urlLastIndex);
1454
1455                if (conn == null) {
1456                        urlIndex = 0;
1457                        url = urlList.get(urlIndex);
1458                }
1459
1460                return conn;
1461        }
1462
1463        /**
1464         * Abort connection by hash code
1465         * @param hashCode - Connection hash code
1466         * */
1467        protected void abort(Integer hashCode) {
1468                log.debug("Abort Connection {}", hashCode);
1469                Connection conn = connectionPool.getConnectionMark().get(hashCode);
1470                if (conn != null) {
1471                        try {
1472                                callAbortConn(conn);
1473                                log.debug("Aborted Connection -> {}", hashCode);
1474                                connectionPool.removeAll(hashCode);
1475                                log.debug("Abort(If Aborted,{}), UsedPoolSize={}.", hashCode, getOpenConnectionSize()); 
1476                        } catch (Exception e) {
1477                                log.warn(e.getMessage(), e);
1478
1479                        }
1480                }
1481        }
1482
1483        /**
1484         * Call abort connection 
1485         * Please see 'abortMode' options (rollback,close,abort)
1486         * Default is 'abort'
1487         * @param conn - Connection
1488         * */
1489        private void callAbortConn(Connection conn) {
1490                try {
1491                        if (abortMode.equalsIgnoreCase("rollback")) {
1492                                if (!conn.getAutoCommit())
1493                                        conn.rollback();
1494
1495                                conn.close();
1496                        } else if (abortMode.equalsIgnoreCase("close")) {
1497                                conn.close();
1498                        } else {
1499                                conn.abort(Executors.newFixedThreadPool(1));
1500                        }
1501                } catch (SQLException e) {
1502                        log.warn("EROR CODE: {}", e.getErrorCode());
1503                        log.warn(e.getMessage(), e);
1504
1505                } catch (Exception e) {
1506                        log.warn(e.getMessage(), e);
1507
1508                }
1509        }
1510
1511        /**
1512         * Load jdbc driver jar
1513         * @param driverJar - It is jar file path or jar dir path
1514         * @param driver
1515         * @exception SQLException
1516         * @return Driver
1517         * */
1518        private synchronized Driver loadJdbcDriver(String driverJar, String driver) throws SQLException {
1519                try {
1520
1521                        if (driverJar == null) {
1522                                int errorCode = 99901;
1523                                String errorMsg = "Driver jar is null.";
1524                                log.warn(String.format("ERROR CODE: (%s) %s", errorCode, errorMsg));
1525                                throw new SQLException(errorMsg) {
1526                                        @Override
1527                                        public int getErrorCode() {
1528                                                return errorCode;
1529                                        }
1530                                };
1531                        }
1532
1533                        Driver driverNewInstance = DRIVER_MARK.get(driverJar);
1534
1535                        if (driverNewInstance == null) {
1536                                URLClassLoader driverClassLoader = CommonTools.loadJars(driverJar);
1537                                Class classToLoad = Class.forName(driver, true, driverClassLoader);
1538                                Constructor driverConstructor = classToLoad.getConstructor();
1539                                driverConstructor.setAccessible(true);
1540                                driverNewInstance = (Driver) driverConstructor.newInstance();
1541
1542                                DRIVER_MARK.put(driverJar, driverNewInstance);
1543                        }
1544
1545                        return driverNewInstance;
1546                } catch (Exception e) {
1547                        int errorCode = 99902;
1548                        String errorMsg = e.getMessage();
1549                        log.warn(String.format("ERROR CODE: (%s) %s", errorCode, errorMsg));
1550                        throw new SQLException(e) {
1551
1552                                @Override
1553                                public int getErrorCode() {
1554                                        return errorCode;
1555                                }
1556
1557                        };
1558                }
1559        }
1560
1561        /**
1562         * Get owner thread name
1563         * For use sync DataSource
1564         * @return String
1565         * */
1566        public String getOwner() {
1567                return owner;
1568        }
1569
1570        /**
1571         * Get config properties
1572         * @return ConfigProperties
1573         * */
1574        public ConfigProperties getConfigProperties() {
1575                return configProperties;
1576        }
1577
1578        /**
1579         * Get sync data source list
1580         * @exception SQLException
1581         * @param owner - From master data source thread name
1582         * @return List<DriverDataSource>
1583         * */
1584        protected final synchronized List<DriverDataSource> getSyncDataSourceList(String owner) throws SQLException {
1585                if (syncDriverDataSource == null) {
1586                        syncDriverDataSource = new ArrayList<DriverDataSource>();
1587                        List<String> syncDsPropList = configProperties.getArray("SyncDataSource");
1588
1589                        if (syncDsPropList == null)
1590                                return null;
1591
1592                        for (String syncDsProp : syncDsPropList) {
1593
1594                                if (CommonTools.isBlank(syncDsProp))
1595                                        continue;
1596
1597                                File _appDsFile = new File(syncDsProp);
1598                                if (_appDsFile.exists()) {
1599                                        DriverDataSource dds = new DriverDataSource(_appDsFile);
1600                                        dds.owner = owner;
1601                                        syncDriverDataSource.add(dds);
1602                                } else {
1603                                        int errorCode = 99905;
1604                                        String errorMsg = String.format("SyncDatasource is null - %s .", syncDsProp);
1605                                        throw new SQLException(errorMsg) {
1606                                                @Override
1607                                                public int getErrorCode() {
1608                                                        return errorCode;
1609                                                }
1610                                        };
1611                                }
1612
1613                        }
1614                }
1615                return syncDriverDataSource;
1616        }
1617
1618        /**
1619         * For checksum master and sync data source connection url
1620         * Pause sync where same connection url
1621         * @return String
1622         * */
1623        public String getFingerprint() {
1624                return CommonTools.md5(String.format("%s-%s", this.url, this.username));
1625        }
1626        
1627        public synchronized double getAveUsage(){
1628            int size = aveUsageSum.size();
1629            double sum = 0.0D;
1630            for(int i = 0; i < size;i++){
1631                sum += aveUsageSum.get(i);
1632            }
1633            
1634            if(size <= 0) return 0.0D;
1635            
1636            return sum/size;
1637        }
1638        
1639        public void setMaxAveUsageSize(Integer maxAveUsageSize){
1640            this.maxAveUsageSize = maxAveUsageSize == null ? 10000 : maxAveUsageSize;
1641        }
1642
1643        private synchronized void countUsage(boolean calAve) {
1644                double usage = getUsage();
1645        if(calAve){
1646            if(aveUsageSum.size() >= maxAveUsageSize){
1647                        aveUsageSum.remove(0);
1648            }
1649            aveUsageSum.add(usage);
1650        }
1651                if (usage > maxUsage) {
1652                        maxUsage = usage;
1653                }
1654        }
1655
1656        /**
1657         * Get pool using 
1658         * @return double
1659         * */
1660        public synchronized double getUsage() {
1661                Map<Integer, Date> CONNECTION_USING_AT_MARK_SYNC = connectionPool.getConnectionUsingAtMark();
1662                int usingSize = CONNECTION_USING_AT_MARK_SYNC.keySet().size();
1663
1664                if (usingSize <= 0)
1665                        return 0.0D;
1666
1667                if (maxPoolSize <= 0)
1668                        return 0.0D;
1669                        
1670                return usingSize / (maxPoolSize * 1.0D);
1671        }
1672
1673        /** Abandon the following methods **/
1674
1675        @Override
1676        public java.lang.Object unwrap(java.lang.Class arg0) throws java.sql.SQLException {
1677                return null;
1678        }
1679
1680        @Override
1681        public boolean isWrapperFor(java.lang.Class arg0) throws java.sql.SQLException {
1682                return false;
1683        }
1684
1685        @Override
1686        public java.util.logging.Logger getParentLogger() throws java.sql.SQLFeatureNotSupportedException {
1687                return null;
1688        }
1689
1690        @Override
1691        public void setLogWriter(PrintWriter arg0) throws SQLException {
1692        }
1693
1694        @Override
1695        public PrintWriter getLogWriter() throws SQLException {
1696                return null;
1697        }
1698
1699        @Override
1700        public int getLoginTimeout() throws SQLException {
1701                return 0;
1702        }
1703
1704        @Override
1705        public void setLoginTimeout(int arg0) throws SQLException {
1706        }
1707
1708        @Override
1709        public Connection getConnection(String arg0, String arg1) throws SQLException {
1710                return null;
1711        }
1712
1713        @Override
1714        public ConnectionBuilder createConnectionBuilder() throws SQLException {
1715                return null;
1716        }
1717
1718}