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                                                long timeoutSec = (currentTime.getTime() - openedAt.getTime()) / 1000;
561                                                Long _timeout = conn.getAutoCommit() ? timeout : transactionTimeout;
562
563                                                if (_timeout == 0)
564                                                        continue;
565
566                                                if (timeoutSec >= _timeout) {
567                                                        if (!abortList.contains(hashCode))
568                                                                abortList.add(hashCode);
569                                                }
570                                        }
571                                }
572                                for (Integer hashCode : abortList) {
573                                        countAbortedTimeout++;
574                                        log.mark("AbortTimeoutConnection '{}', ConnectionPoolSize '{}', OpenConnectionSize '{}'.",hashCode,connectionPool.size(),getOpenConnectionSize());
575                                        abort(hashCode);
576                                }
577                        }
578                } catch (Exception e) {
579                        log.warn(e.getMessage(), e);
580                }
581        }
582
583        /**
584         * Abort life cycle connection
585         * Skip execute when 'lifeCycle' is 0
586         * */
587        private synchronized void abortLifeCycleEndedConnection() {
588                if (lifeCycle > 0) {
589                        Date currentTime = new Date();
590                        List<Integer> abortList = new ArrayList<Integer>();
591                        Map<Integer,Date> openedAtMark = connectionPool.getConnectionOpenedAtMark();
592                        Set<Integer> hashCodeList = openedAtMark.keySet();
593                        for (Integer hashCode : hashCodeList) {
594                                Date openedAt = openedAtMark.get(hashCode);
595                                if (openedAt != null) {
596                                        long lifeSec = (currentTime.getTime() - openedAt.getTime()) / 1000;
597                                        if (lifeSec >= lifeCycle && connectionPool.contains(hashCode)) {
598                                                if (!abortList.contains(hashCode))
599                                                        abortList.add(hashCode);
600                                        }
601                                }
602                        }
603                        for (Integer hashCode : abortList) {
604                                if (connectionPool.contains(hashCode)) {
605                                        countAbortedLifeCycle++;
606                                        log.mark("AbortLifeCycleEndedConnection '{}', ConnectionPoolSize '{}', OpenConnectionSize '{}'.",hashCode,connectionPool.size(),getOpenConnectionSize());
607                                        abort(hashCode);
608                                }
609                        }
610                }
611        }
612
613        /**
614         * Abort idle timeout connection
615         * Skip execute when 'idleTimeout' is 0
616         * */
617        private synchronized void abortIdleTimeoutConnection() {
618                if (idleTimeout > 0) {
619                        Date currentTime = new Date();
620                        List<Integer> abortList = new ArrayList<Integer>();
621                        Map<Integer,Date> idleBeginMark = connectionPool.getConnectionIdleBeginMark();
622                        Set<Integer> hashCodeList = idleBeginMark.keySet();
623                        for (Integer hashCode : hashCodeList) {
624                                Date openedAt = idleBeginMark.get(hashCode);
625                                if (openedAt != null) {
626                                        long idleTime = (currentTime.getTime() - openedAt.getTime()) / 1000;
627                                        if (idleTime >= idleTimeout && connectionPool.contains(hashCode)) {
628                                                if (!abortList.contains(hashCode))
629                                                        abortList.add(hashCode);
630                                        }
631                                }
632                        }
633                        for (Integer hashCode : abortList) {
634                                if (connectionPool.contains(hashCode)) {
635                                        int loadPoolSize = connectionPool.size();
636                                        boolean overMinPoolSize = loadPoolSize > minPoolSize;
637                                        log.debug("Check abort idle connection, overMinPoolSize[%s] = loadPoolSize[%s] > minPoolSize[%s].",overMinPoolSize, loadPoolSize,minPoolSize);
638                                        if (overMinPoolSize && connectionPool.contains(hashCode)) {
639                                                countAbortedIdle++;
640                                                log.mark("AbortIdleTimeoutConnection '{}', ConnectionPoolSize '{}', OpenConnectionSize '{}'.",hashCode,connectionPool.size(),getOpenConnectionSize());
641                                                abort(hashCode);
642                                        }
643                                }
644                        }
645                }
646        }
647
648        /**
649         * Abort invalid connection
650         * Skip execute when 'testValidTimeout' is 0
651         * */
652        private synchronized void abortInvalidConnection() {
653                if (testValidTimeout > 0) {
654                        try {
655                                List<Integer> abortList = new ArrayList<Integer>();
656                                Map<Integer,Date> idleBeginMark = connectionPool.getConnectionIdleBeginMark();
657                                Set<Integer> hashCodeList = idleBeginMark.keySet();
658                                for (Integer hashCode : hashCodeList) {
659                                        Connection conn = connectionPool.getConnectionMark().get(hashCode);
660                                        if (conn != null) {
661                                                boolean valid = conn.isValid(testValidTimeout.intValue());
662                                                if (!valid && !abortList.contains(hashCode))
663                                                        abortList.add(hashCode);
664                                        }
665                                }
666                                for (Integer hashCode : abortList) {
667                                        if (connectionPool.contains(hashCode)) {
668                                                countAbortedInvalid++;
669                                                log.mark("AbortInvalidConnection '{}', ConnectionPoolSize '{}', OpenConnectionSize '{}'.",hashCode,connectionPool.size(),getOpenConnectionSize());
670                                                abort(hashCode);
671                                        }
672                                }
673                        } catch (Exception e) {
674                                log.warn(e.getMessage(), e);
675
676                        }
677                }
678        }
679
680        /**
681         * Create connection to pool
682         * When,
683         *   (loadPoolSize + usedPoolSize) < maxPoolSize
684         *   and
685         *   loadPoolSize < minPoolSize
686         * */
687        private synchronized void createConnectionToPool() {
688                final String threadName = String.format("%s-createConnectionToPool", this.name);
689
690                boolean lessThanMaxPoolSize = getOpenConnectionSize() < maxPoolSize;
691                boolean lessThanMinPoolSize = connectionPool.size() < minPoolSize;
692                boolean lessThanMaxUsage = getUsage() < 1.0D;
693
694                if (log.isDebugEnabled()) {
695                        log.debug("boolean lessThanMaxPoolSize[{}] = getOpenConnectionSize()[{}] < maxPoolSize[{}];",
696                                        lessThanMaxPoolSize, getOpenConnectionSize(), maxPoolSize);
697                        log.debug("boolean lessThanMinPoolSize[{}] = connectionPool.size()[{}] < minPoolSize[{}];",
698                                        lessThanMinPoolSize, connectionPool.size(), minPoolSize);
699                        log.debug("boolean lessThanMaxUsage[{}] = getUsage()[{}] < 1.0D;", lessThanMaxUsage, getUsage());
700                        log.debug("Checking create conditions,LessThanMaxPoolSize={}, LessThanMinPoolSize={}, lessThanMaxUsage={}",
701                                        lessThanMaxPoolSize, lessThanMinPoolSize, lessThanMaxUsage);
702
703                }
704                if (lessThanMaxPoolSize && lessThanMinPoolSize && lessThanMaxUsage) {
705                        int willCreateConnTotal = minPoolSize - connectionPool.size();
706                        if (willCreateConnTotal > 0) {
707                                log.debug("Will create connection total = {}", willCreateConnTotal);
708                        }
709                        for (int i = 0; i < willCreateConnTotal; i++) {
710                                Callable callable = new Callable<Boolean>() {
711                                        @Override
712                                        public Boolean call() {
713                                                try {
714                                                    Thread.currentThread().setName("call-" + threadName);
715                                                        if (closed || Thread.currentThread().isInterrupted()){
716                                                                return false;
717                                                        }
718
719                                                        boolean lessThanMaxPoolSize = getOpenConnectionSize() < maxPoolSize;
720                                                        boolean lessThanMinPoolSize = connectionPool.size() < minPoolSize;
721                                                        boolean lessThanMaxUsage = getUsage() < 1.0D;
722
723                                                        if (lessThanMaxPoolSize && lessThanMinPoolSize && lessThanMaxUsage) {
724                                                                Thread.currentThread().setName(threadName);
725                                                                loadConnectionToPool();
726                                                                log.debug("Loaded connection to pool");
727                                                        }
728                                                } catch (Exception e) {
729                                                        log.warn(e.getMessage(), e);
730                                                }
731                                                return true;
732                                        }
733                                };
734                                connectionPoolExecutor.submit(callable);
735                        }
736                        log.debug("Creating {} connection to pool.", willCreateConnTotal);
737                }
738        }
739
740        /**
741         * Set internal config
742         * @param _configProperties
743         * */
744        private synchronized void setInternalConfig(ConfigProperties _configProperties) {
745                this.configProperties = _configProperties;
746                if (this.configProperties.containsKey("EnableStatusLog"))
747                        setEnableStatusLog(this.configProperties.getBoolean("EnableStatusLog", false));
748
749                if (this.configProperties.containsKey("EnableFullStatusLog"))
750                        setEnableFullStatusLog(this.configProperties.getBoolean("EnableFullStatusLog", true));
751
752                if (this.configProperties.containsKey("StatusLogArchiveDays"))
753                        setStatusLogArchiveDays(this.configProperties.getInteger("StatusLogArchiveDays", 30));
754
755                if (this.configProperties.containsKey("StatusLogFolder"))
756                        setStatusLogFolder(this.configProperties.getString("StatusLogFolder", null));
757
758                if (this.configProperties.containsKey("MaxPoolSize"))
759                        setMaxPoolSize(this.configProperties.getInteger("MaxPoolSize", Integer.MAX_VALUE));
760
761                if (this.configProperties.containsKey("MinPoolSize"))
762                        setMinPoolSize(this.configProperties.getInteger("MinPoolSize", 0));
763
764                if (this.configProperties.containsKey("LifeCycle"))
765                        setLifeCycle(this.configProperties.getSeconds("LifeCycle", Long.MAX_VALUE));
766
767                if (this.configProperties.containsKey("IdleTimeout"))
768                        setIdleTimeout(this.configProperties.getSeconds("IdleTimeout", 0L));
769
770                if (this.configProperties.containsKey("Timeout"))
771                        setTimeout(this.configProperties.getSeconds("Timeout", 0L));
772
773                if (this.configProperties.containsKey("TransactionTimeout"))
774                        setTransactionTimeout(this.configProperties.getSeconds("TransactionTimeout", 0L));
775
776                if (this.configProperties.containsKey("TestValidTimeout"))
777                        setTestValidTimeout(this.configProperties.getSeconds("TestValidTimeout", 5L));
778
779                if (this.configProperties.containsKey("RefreshPoolTimer"))
780                        setRefreshPoolTimer(this.configProperties.getMilliSeconds("RefreshPoolTimer", 1000L));
781
782                if (this.configProperties.containsKey("DriverJar"))
783                        setDriverJar(this.configProperties.getString("DriverJar", null));
784
785                if (this.configProperties.containsKey("ConnProps"))
786                        setConnProps(this.configProperties.getString("ConnProps", null));
787
788                if (this.configProperties.containsKey("ThreadName"))
789                        setName(this.configProperties.getString("ThreadName", null));
790
791                if (this.configProperties.containsKey("Driver"))
792                        setDriverClassName(this.configProperties.getString("Driver", null));
793
794                if (this.configProperties.containsKey("Url"))
795                        setUrl(this.configProperties.getString("Url", null));
796
797                if (this.configProperties.containsKey("Url[0]"))
798                        setUrl(this.configProperties.getArray("Url"));
799
800                if (this.configProperties.containsKey("Username"))
801                        setUsername(this.configProperties.getString("Username", null));
802
803                if (this.configProperties.containsKey("Password"))
804                        setPassword(this.configProperties.getString("Password", null));
805
806                if (this.configProperties.containsKey("AbortMode"))
807                        setAbortMode(this.configProperties.getString("AbortMode", "abort"));
808
809                if (this.configProperties.containsKey("MaxAveUsageSize"))
810                        setMaxAveUsageSize(this.configProperties.getInteger("MaxAveUsageSize", 10000));
811                        
812                loadedConfig = true;
813        }
814
815        /**
816         * Set internal config
817         * @param configPropertiesContent
818         * */
819        private synchronized void setInternalConfig(String configPropertiesContent) throws IOException {
820                log.debug("setInternalConfig -> {}", configPropertiesContent);
821                ConfigProperties _configProperties = new ConfigProperties();
822                _configProperties.load(new StringReader(configPropertiesContent));
823                setInternalConfig(_configProperties);
824        }
825
826        /**
827         * Set config
828         * @param configPropertiesContent
829         * */
830        public synchronized void setConfig(String configPropertiesContent) throws IOException {
831                setInternalConfig(configPropertiesContent);
832        }
833
834        /**
835         * Set config
836         * @param _configProperties
837         * */
838        public synchronized void setConfig(ConfigProperties _configProperties) throws IOException {
839                setInternalConfig(_configProperties);
840        }
841
842        /**
843         * Reload config
844         * When 'appDataSource' is file object
845         * */
846        private synchronized void reloadConfig() {
847            try{
848            DriverDataSource the = this;
849            if (this.appDataSource != null) {
850                configProperties = new ConfigProperties();
851                Runnable updateRun = new Runnable(){
852                    @Override
853                    public void run(){
854                        try{
855                                the.setInternalConfig(configProperties);
856                                Logger.systemMark(DriverDataSource.class, "DriverDataSource '{}' Reloaded", name);
857                        }catch(Exception e){
858                            log.error(e.getMessage(),e);
859                        }
860                    }
861                };
862                configProperties.load(this.appDataSource,updateRun);
863                updateRun.run();
864            }
865            }catch(Exception e){
866                log.error(e.getMessage(),e);
867            }
868        }
869
870        /**
871         *  Archive log
872         * */
873        private void archiveLog() {
874                if (statusLogFile != null && statusLogArchiveDays != null) {
875                        File statusLogFileParent = statusLogFile.getParentFile();
876                        if (statusLogArchiveDays > 0 && statusLogFileParent != null) {
877                                try {
878                                        long fileOutArchiveDaysMs = new Date().getTime() - (statusLogArchiveDays * 24 * 3600000L);
879                                        deleteFilesOlderThan(statusLogFileParent, fileOutArchiveDaysMs);
880                                } catch (Exception e) {
881                                        log.warn(e);
882
883                                }
884                        }
885                }
886        }
887
888        /**
889         * Delete old archive log
890         * */
891        private void deleteFilesOlderThan(File directory, long fileOutArchiveDaysMs) throws IOException {
892                if (directory.isDirectory()) {
893                        File[] files = directory.listFiles();
894                        if (files != null) {
895                                for (File file : files) {
896                                        if (file.isFile()) {
897                                                boolean isLogFile = file.getName().toLowerCase().endsWith(".log");
898                                                if (isLogFile) {
899                                                        boolean canWrite = file.canWrite();
900                                                        if (canWrite) {
901                                                                long lastModified = file.lastModified();
902                                                                if (lastModified < fileOutArchiveDaysMs) {
903                                                                        Files.deleteIfExists(Paths.get(file.toURI()));
904                                                                }
905                                                        }
906                                                }
907                                        }
908                                }
909                        }
910                }
911        }
912
913        /**
914         * Write connection status log
915         * */
916        private synchronized void statusLogOutput() {
917                File folder = statusLogFile.getParentFile();
918                if (folder.exists()) {
919                        if (!folder.canWrite())
920                                return;
921                } else {
922                        folder.mkdirs();
923                }
924                if (statusLogFile.exists()) {
925                        if (!statusLogFile.canWrite())
926                                return;
927                }
928
929                SimpleDateFormat timeFormat = new SimpleDateFormat("yyyyMMdd HH:mm:ss.SSS");
930                Map<Integer, String> CONNECTION_THREAD_MARK_SYNC = connectionPool.getConnectionThreadMark();
931                Map<Integer, Date> CONNECTION_OPENED_AT_MARK_SYNC = connectionPool.getConnectionOpenedAtMark();
932                Map<Integer, Date> CONNECTION_USING_AT_MARK_SYNC = connectionPool.getConnectionUsingAtMark();
933                Map<Integer, Date> CONNECTION_IDLE_BEGIN_MARK_SYNC = connectionPool.getConnectionIdleBeginMark();
934
935                int usingSize = CONNECTION_USING_AT_MARK_SYNC.keySet().size();
936                int idleSize = CONNECTION_IDLE_BEGIN_MARK_SYNC.keySet().size();
937
938                boolean isFull = (usingSize > idleSize && idleSize == 0 && usingSize >= maxPoolSize);
939
940                String driverInfo = String.format("Driver: %s%s", this.driverClassName, System.lineSeparator());
941                StringBuffer countInfoSb = new StringBuffer();
942                countInfoSb.append(
943                                String.format("MinPoolSize: %s\tMaxPoolSize: %s%s", minPoolSize, maxPoolSize, System.lineSeparator()));
944                countInfoSb.append(String.format("RefreshPoolTimer: %sms%s", refreshPoolTimer, System.lineSeparator()));
945                countInfoSb.append(String.format("TestValidTimeout: %ss\tIdleTimeout: %ss%s", testValidTimeout, idleTimeout,
946                                System.lineSeparator()));
947                countInfoSb.append(String.format("Timeout: %ss\tTransactionTimeout: %ss%s", timeout, transactionTimeout,
948                                System.lineSeparator()));
949                countInfoSb.append(
950                                String.format("LifeCycle: %ss\tCountClosed: %s%s", lifeCycle, countClosed, System.lineSeparator()));
951                countInfoSb.append(String.format("CountCreatedConnection: %s\tCountAbortedCloseFailed: %s%s",
952                                countCreatedConnection, countAbortedCloseFailed, System.lineSeparator()));
953                countInfoSb.append(String.format("CountAbortedIdleTimeouted: %s\tCountAbortedTimeouted: %s%s", countAbortedIdle,
954                                countAbortedTimeout, System.lineSeparator()));
955                countInfoSb.append(String.format("CountAbortedLifeCycleEnded: %s\tCountAbortedInvalid: %s%s",
956                                countAbortedLifeCycle, countAbortedInvalid, System.lineSeparator()));
957                countInfoSb.append(String.format("MaxUsage: %.2f%%\tCurrentUsage: %.2f%%\tAveUsage: %.2f%%\tCounter: %s/%s%s", maxUsage * 100,
958                                getUsage() * 100,getAveUsage() * 100, aveUsageSum.size(),maxAveUsageSize,System.lineSeparator()));
959
960                String urlInfo = String.format("URL: %s%s", this.url, System.lineSeparator());
961                String usernameInfo = String.format("User: %s%s", this.username, System.lineSeparator());
962                String header = String.format("%sLine\tHashCode\tCreatedTime\tChangedTime\tStatus\tLastUsingBy%s",
963                                System.lineSeparator(), System.lineSeparator());
964                String lineFormat = "%s\t%s\t%s\t%s\t%s\t%s%s";
965                StringBuffer sbf = new StringBuffer();
966                sbf.append(String.format("ThreadName: %s%s", this.name, System.lineSeparator()));
967                sbf.append(String.format("Host: %s\tPID: %s\tSystemTime: %s%s", CommonTools.getHostname(), PID,
968                                timeFormat.format(new Date()), System.lineSeparator()));
969                sbf.append(urlInfo);
970                sbf.append(usernameInfo);
971                sbf.append(driverInfo);
972                sbf.append(countInfoSb.toString());
973                sbf.append(header);
974                int lineNumber = 1;
975
976                Set<Integer> keys = CONNECTION_OPENED_AT_MARK_SYNC.keySet();
977                List<Object[]> list = new ArrayList<Object[]>();
978                for (Integer hashCode : keys) {
979                        String status = "-";
980                        String thread = "-";
981                        Date changedTime = null;
982                        Date createdTime = CONNECTION_OPENED_AT_MARK_SYNC.get(hashCode);
983
984                        if (CONNECTION_THREAD_MARK_SYNC.containsKey(hashCode)) {
985                                thread = CONNECTION_THREAD_MARK_SYNC.get(hashCode);
986                        }
987
988                        if (CONNECTION_USING_AT_MARK_SYNC.containsKey(hashCode)) {
989                                changedTime = CONNECTION_USING_AT_MARK_SYNC.get(hashCode);
990                                status = "Using";
991                        } else if (CONNECTION_IDLE_BEGIN_MARK_SYNC.containsKey(hashCode)) {
992                                changedTime = CONNECTION_IDLE_BEGIN_MARK_SYNC.get(hashCode);
993                                status = "Idle";
994                        }
995
996                        Object[] objArr = new Object[] { hashCode, createdTime, changedTime, status, thread };
997                        list.add(objArr);
998                }
999
1000                Comparator comp = new Comparator() {
1001                        @Override
1002                        public int compare(Object arg0, Object arg1) {
1003                                Object[] objArr0 = (Object[]) arg0;
1004                                Object[] objArr1 = (Object[]) arg1;
1005                                Date date0 = (Date) objArr0[1];
1006                                Date date1 = (Date) objArr1[1];
1007                                return date0.getTime() < date1.getTime() ? -1 : 1;
1008                        }
1009                };
1010
1011                Collections.sort(list, comp);
1012
1013                for (Object[] objArr : list) {
1014                        Integer hashCode = (Integer) objArr[0];
1015                        Date createdTime = (Date) objArr[1];
1016                        String createdTimeStr = createdTime == null ? "-" : timeFormat.format(createdTime);
1017                        Date changedTime = (Date) objArr[2];
1018                        String changedTimeStr = changedTime == null ? "-" : timeFormat.format(changedTime);
1019                        String status = objArr[3].toString();
1020                        String thread = objArr[4].toString();
1021                        String line = String.format(lineFormat, lineNumber, hashCode, createdTimeStr, changedTimeStr, status,
1022                                        thread, System.lineSeparator());
1023                        sbf.append(line);
1024                        lineNumber++;
1025                }
1026
1027                try {
1028                        String msg = sbf.toString();
1029                        FileTools.write(statusLogFile, msg, false);
1030
1031                        if (isFull && enableFullStatusLog) {
1032                                SimpleDateFormat timeFullFormat = new SimpleDateFormat("yyyyMMddHHmm");
1033                                File fullStatusLogFile = new File(String.format("%s_full_%s", statusLogFile.getAbsolutePath(),
1034                                                timeFullFormat.format(new Date())));
1035                                FileTools.write(fullStatusLogFile, msg, false);
1036
1037                        }
1038                } catch (IOException e) {
1039                        log.warn(e.getMessage(), e);
1040
1041                }
1042        }
1043
1044        /**
1045         * Manage connection pool
1046         * */
1047        private synchronized void manageConnectionPool() {
1048                if (!inited) {
1049                        inited = true;
1050
1051                        String threadName = this.name;
1052                        ExecutorService executor = Executors.newFixedThreadPool(2);
1053                        executor.execute(new Runnable() {
1054
1055                                @Override
1056                                public void run() {
1057                                        Thread.currentThread().setName(String.format("%s-manageConnectionPool-1", threadName));
1058                                        while (true) {
1059                                                if (closed || Thread.currentThread().isInterrupted())
1060                                                        break;
1061
1062                                                try {
1063                                                        abortLifeCycleEndedConnection();
1064                                                        abortTimeoutConnection();
1065                                                        abortIdleTimeoutConnection();
1066                                                        createConnectionToPool();
1067                                                        countUsage(true);
1068                                                        if (enableStatusLog && statusLogFile != null) {
1069                                                                statusLogOutput();
1070                                                                archiveLog();
1071                                                        }
1072                                                } catch (Exception e) {
1073                                                        log.warn(e.getMessage(), e);
1074                                                }
1075
1076                                                try {
1077                                                        Thread.sleep(refreshPoolTimer);
1078                                                } catch (InterruptedException e) {
1079                                                        log.warn(e.getMessage(), e);
1080                                                }
1081                                        }
1082                                }
1083
1084                        });
1085
1086                        executor.execute(new Runnable() {
1087
1088                                @Override
1089                                public void run() {
1090                                        Thread.currentThread().setName(String.format("%s-manageConnectionPool-2", threadName));
1091                                        while (true) {
1092                                                if (closed || Thread.currentThread().isInterrupted())
1093                                                        break;
1094
1095                                                try {
1096                                                        abortInvalidConnection();
1097                                                } catch (Exception e) {
1098                                                        log.warn(e.getMessage(), e);
1099
1100                                                }
1101
1102                                                try {
1103                                                        Thread.sleep(refreshPoolTimer);
1104                                                } catch (InterruptedException e) {
1105                                                        log.warn(e.getMessage(), e);
1106                                                }
1107                                        }
1108                                }
1109
1110                        });
1111                }
1112        }
1113
1114        /**
1115         * Get connection
1116         * @exception SQLException
1117         * @return Connection
1118         * */
1119    @Override
1120        public synchronized Connection getConnection() throws SQLException {
1121            try{
1122                manageConnectionPool();
1123                Date currentTime = new Date();
1124                Connection conn = null;
1125                boolean allowedGetSingle = true;
1126
1127                if(connectionPool.size() == 0){
1128                     loadConnectionToPool();
1129                }
1130
1131                conn = connectionPool.getConnection();
1132                if (conn == null) {
1133                        throwConnIsNull();
1134                }                       
1135            countUsage(false);
1136                
1137                if (conn == null) {
1138                        throwConnIsNull();
1139                }
1140                log.debug("Get Connection -> {}", conn.hashCode());
1141                return new DriverConnection(this, conn);
1142            }catch(Exception e){
1143                log.error(e.getMessage(),e);
1144                throwConnIsNull();
1145            }
1146            return null;
1147        }
1148        
1149        private void throwConnIsNull() throws SQLException {
1150        int errorCode = 99904;
1151                String errorMsg = "Connection is null.";
1152                log.warn(String.format("ERROR CODE: (%s) %s", errorCode, errorMsg));
1153                throw new SQLException(errorMsg) {
1154                        @Override
1155                        public int getErrorCode() {
1156                                return errorCode;
1157                        }
1158                };
1159        }
1160
1161        private synchronized void checkConnectionPoolSize() throws SQLException {
1162                int openConnectionSize = getOpenConnectionSize();
1163                if (openConnectionSize >= maxPoolSize) {
1164                        int errorCode = 99903;
1165                        String errorMsg = String.format("Full connection pool,current size is %s.", openConnectionSize);
1166                        log.warn(String.format("ERROR CODE: (%s) %s", errorCode, errorMsg));
1167                        throw new SQLException(errorMsg) {
1168                                @Override
1169                                public int getErrorCode() {
1170                                        return errorCode;
1171                                }
1172                        };
1173
1174                }
1175        }
1176
1177        /**
1178         * Get connection properties
1179         * @return Properties
1180         * */
1181        private Properties getConnectionProperties() {
1182                Properties props = new Properties();
1183                props.setProperty("user", username);
1184                props.setProperty("password", password);
1185
1186                if (!CommonTools.isBlank(connProps)) {
1187                        try {
1188                                String[] ps = connProps.split(";");
1189                                int psSize = ps.length;
1190                                for (int i = 0; i < psSize; i++) {
1191                                        String[] propKeyValue = ps[i].split("=");
1192                                        String pkey = propKeyValue[0].trim();
1193                                        String pval = propKeyValue[1].trim();
1194                                        props.setProperty(pkey, pval);
1195                                }
1196                        } catch (Exception e) {
1197                                log.warn(e.getMessage(), e);
1198
1199                        }
1200                }
1201                return props;
1202        }
1203
1204        /**
1205         * Get connection from driver
1206         * @exception SQLException
1207         * @return Connection
1208         * */
1209        private synchronized Connection getConnectionFromDriver() throws SQLException {
1210                Properties props = getConnectionProperties();
1211                Connection conn = null;
1212                if (driverJar == null) {
1213                        conn = tryGetConnection(null, props);
1214                } else {
1215                        Driver driver = loadJdbcDriver(driverJar, driverClassName);
1216                        log.debug("Success init drive -> {}", driverClassName);
1217                        conn = tryGetConnection(driver, props);
1218                }
1219
1220                if (conn == null) {
1221                        throwConnIsNull();
1222                }
1223                return conn;
1224        }
1225
1226        /**
1227         * Load connection to pool
1228         * @exception SQLException
1229         * */
1230        private synchronized void loadConnectionToPool() throws SQLException {
1231                checkConnectionPoolSize();
1232                log.debug("Opening(Pool)");
1233                final Connection conn = getConnectionFromDriver();
1234                final Integer hashCode = conn.hashCode();
1235                log.debug("Created Connection -> {}", hashCode);
1236                connectionPool.add(conn);
1237        }
1238 
1239        /**
1240         * Close connection
1241         * @exception SQLException 
1242         * @param conn - Connection object
1243         * */
1244        protected void closeConnection(Connection conn) throws SQLException {
1245                if (conn != null)
1246                        closeConnection(conn.hashCode());
1247        }
1248
1249        /**
1250         * Close connection 
1251         * @exception SQLException
1252         * @param hashCode - Connection hash code
1253         * */
1254        protected void closeConnection(Integer hashCode) throws SQLException {
1255                Connection conn = connectionPool.getConnectionMark().get(hashCode);
1256                if (conn != null) {
1257                        try {
1258                                synchronized (this) {
1259                                        connectionPool.removeConnectionUsingAtMark(hashCode);
1260                                }
1261                                if (lifeCycle <= 0) {
1262                                        conn.close();
1263                                        connectionPool.removeAll(hashCode);
1264                                        countClosed++;
1265                                        log.debug("Closed Connection -> {}", hashCode);
1266                                } else {
1267                                    if(closed){
1268                                        conn.close();
1269                                                connectionPool.removeAll(hashCode);
1270                                                countClosed++;
1271                                        log.debug("Closed Connection -> {}", hashCode);
1272                                    }else{
1273                                        conn.setAutoCommit(true);
1274                                        connectionPool.freed(hashCode);
1275                                        log.debug("Freed Connection -> {}", hashCode);
1276                                    }
1277                                }
1278                        } catch (SQLException e) {
1279                                addCountAbortedCloseFailed(+1);
1280                                abort(hashCode);
1281
1282                                throw e;
1283                        }
1284                }
1285        }
1286
1287        /**
1288         * Add count abort close failed for write status log
1289         * @param value - Change value
1290         * */
1291        private synchronized void addCountAbortedCloseFailed(int value) {
1292                countAbortedCloseFailed += value;
1293        }
1294
1295        /**
1296         * Close all connections
1297         * */
1298        public synchronized void close() {
1299                closed = true;
1300                log.mark("Try Abort(On SystemExit,CLOSE-{}) connections.",name);
1301                Map<Integer,Connection> connectionMark = connectionPool.getConnectionMark();
1302                Iterator<Integer> itr = connectionMark.keySet().iterator();
1303                while (itr.hasNext()) {
1304                        Integer key = itr.next();
1305                        Connection conn = connectionMark.get(key);
1306                        if (conn != null) {
1307                                log.mark("Abort(On SystemExit) connection '{}.{}'.",name,conn.hashCode());
1308                                abort(conn.hashCode());
1309                        }
1310                }
1311                List<Integer> connList = connectionPool.list();
1312                for(Integer hashCode : connList){
1313                    log.mark("Abort(On SystemExit) connection '{}.{}'.",name,hashCode);
1314                    abort(hashCode);
1315                }               
1316                log.mark("Aborted all connections '{}'.",name);     
1317        }
1318        
1319        /**
1320         * Loop close all connections
1321         * */
1322        public synchronized void closeAll() {
1323                closed = true;
1324                log.mark("Try Abort(On SystemExit,CLOSE_ALL-{}) connections.",name);
1325                List<Integer> connList = connectionPool.list();
1326                for(Integer hashCode : connList){
1327                    log.mark("Abort(On SystemExit) connection '{}.{}'.",name,hashCode);
1328                    abort(hashCode);
1329                }
1330                double currentUsage = getUsage();
1331                log.mark("Checking usage for close all , the '{}' usage is '{}'.",name,currentUsage);
1332                if(currentUsage > 0){
1333                    try{
1334                        Thread.sleep(1000L);
1335                    }catch(InterruptedException e){
1336                        log.error(e.getMessage(),e);
1337                    }
1338                    closeAll();
1339                }
1340                log.mark("Aborted all connections '{}'.",name);
1341        }       
1342        
1343        /**
1344         * Deregister JDBC Driver
1345         * For system exit
1346         * */
1347        public synchronized static void deregisterDriver(){
1348            LoggerFactory.getLogger(DriverDataSource.class).mark("DeregisterDriver processing.");
1349            Enumeration<Driver> drivers = DriverManager.getDrivers();
1350            if(drivers != null){
1351                while (drivers.hasMoreElements()) {
1352                        Driver driver = drivers.nextElement();
1353                        try{
1354                            LoggerFactory.getLogger(DriverDataSource.class).mark("Driver: '{}'",driver.hashCode());
1355                            DriverManager.deregisterDriver(driver);
1356                        }catch(Exception e){
1357                            LoggerFactory.getLogger(DriverDataSource.class).error(e.getMessage(),e);
1358                        }
1359                }
1360            }
1361                LoggerFactory.getLogger(DriverDataSource.class).mark("DeregisterDriver processed.");
1362        }
1363
1364        /**
1365         * Try get connection 
1366         * Support multi url
1367         * @param driver - Driver
1368         * @param props - Connection properties ,var name is 'connProps'
1369         * */
1370        private synchronized Connection tryGetConnection(Driver driver, Properties props) {
1371                Connection conn = null;
1372                int urlLastIndex = urlList.size() - 1;
1373
1374                do {
1375                        if (closed)
1376                                break;
1377
1378                        if (connectionPool.size() >= maxPoolSize)
1379                                break;
1380
1381                        if (!CONN_ERROR_TIME.containsKey(urlIndex)) {
1382                                CONN_ERROR_TIME.put(urlIndex, 0L);
1383                        }
1384
1385                        Long connErrorTime = CONN_ERROR_TIME.get(urlIndex);
1386                        if (connErrorTime > 0) {
1387                                ConfigProperties cp = getConfigProperties();
1388                                long connCheckMs = 10000L;
1389                                if (cp != null) {
1390                                        connCheckMs = cp.getLong("TryGetConnectionCheckTime", 10000L);
1391                                }
1392                                boolean isSkipConn = connErrorTime > 0 && (System.currentTimeMillis() - connErrorTime) <= connCheckMs;
1393                                if (isSkipConn) {
1394                                        try {
1395                                                Thread.sleep(100L);
1396                                        } catch (InterruptedException e) {
1397                                                log.debug(e.getMessage(), e);
1398                                        }
1399                                        continue;
1400                                }
1401                        }
1402
1403                        url = urlList.get(urlIndex);
1404                        try {
1405                                if (driver == null) {
1406                                        Class.forName(driverClassName);
1407                                        conn = DriverManager.getConnection(url, props);
1408                                } else {
1409                                        conn = driver.connect(url, props);
1410                                }
1411                                conn.setAutoCommit(true);
1412
1413                                countCreatedConnection++;
1414
1415                                CONN_ERROR_TIME.put(urlIndex, 0L);
1416
1417                                break;
1418                        } catch (Exception e) {
1419
1420                                CONN_ERROR_TIME.put(urlIndex, System.currentTimeMillis());
1421
1422                                urlIndex += 1;
1423
1424                                Logger.systemError(DriverDataSource.class,"Failed connect to '{}'.", url);
1425                                Logger.systemError(DriverDataSource.class,e.getMessage(), e);
1426
1427                        }
1428                } while (urlIndex <= urlLastIndex);
1429
1430                if (conn == null) {
1431                        urlIndex = 0;
1432                        url = urlList.get(urlIndex);
1433                }
1434
1435                return conn;
1436        }
1437
1438        /**
1439         * Abort connection by hash code
1440         * @param hashCode - Connection hash code
1441         * */
1442        protected void abort(Integer hashCode) {
1443                log.debug("Abort Connection {}", hashCode);
1444                Connection conn = connectionPool.getConnectionMark().get(hashCode);
1445                if (conn != null) {
1446                        try {
1447                                callAbortConn(conn);
1448                                log.debug("Aborted Connection -> {}", hashCode);
1449                                connectionPool.removeAll(hashCode);
1450                                log.debug("Abort(If Aborted,{}), UsedPoolSize={}.", hashCode, getOpenConnectionSize()); 
1451                        } catch (Exception e) {
1452                                log.warn(e.getMessage(), e);
1453
1454                        }
1455                }
1456        }
1457
1458        /**
1459         * Call abort connection 
1460         * Please see 'abortMode' options (rollback,close,abort)
1461         * Default is 'abort'
1462         * @param conn - Connection
1463         * */
1464        private void callAbortConn(Connection conn) {
1465                try {
1466                        if (abortMode.equalsIgnoreCase("rollback")) {
1467                                if (!conn.getAutoCommit())
1468                                        conn.rollback();
1469
1470                                conn.close();
1471                        } else if (abortMode.equalsIgnoreCase("close")) {
1472                                conn.close();
1473                        } else {
1474                                conn.abort(Executors.newFixedThreadPool(1));
1475                        }
1476                } catch (SQLException e) {
1477                        log.warn("EROR CODE: {}", e.getErrorCode());
1478                        log.warn(e.getMessage(), e);
1479
1480                } catch (Exception e) {
1481                        log.warn(e.getMessage(), e);
1482
1483                }
1484        }
1485
1486        /**
1487         * Load jdbc driver jar
1488         * @param driverJar - It is jar file path or jar dir path
1489         * @param driver
1490         * @exception SQLException
1491         * @return Driver
1492         * */
1493        private synchronized Driver loadJdbcDriver(String driverJar, String driver) throws SQLException {
1494                try {
1495
1496                        if (driverJar == null) {
1497                                int errorCode = 99901;
1498                                String errorMsg = "Driver jar is null.";
1499                                log.warn(String.format("ERROR CODE: (%s) %s", errorCode, errorMsg));
1500                                throw new SQLException(errorMsg) {
1501                                        @Override
1502                                        public int getErrorCode() {
1503                                                return errorCode;
1504                                        }
1505                                };
1506                        }
1507
1508                        Driver driverNewInstance = DRIVER_MARK.get(driverJar);
1509
1510                        if (driverNewInstance == null) {
1511                                URLClassLoader driverClassLoader = CommonTools.loadJars(driverJar);
1512                                Class classToLoad = Class.forName(driver, true, driverClassLoader);
1513                                Constructor driverConstructor = classToLoad.getConstructor();
1514                                driverConstructor.setAccessible(true);
1515                                driverNewInstance = (Driver) driverConstructor.newInstance();
1516
1517                                DRIVER_MARK.put(driverJar, driverNewInstance);
1518                        }
1519
1520                        return driverNewInstance;
1521                } catch (Exception e) {
1522                        int errorCode = 99902;
1523                        String errorMsg = e.getMessage();
1524                        log.warn(String.format("ERROR CODE: (%s) %s", errorCode, errorMsg));
1525                        throw new SQLException(e) {
1526
1527                                @Override
1528                                public int getErrorCode() {
1529                                        return errorCode;
1530                                }
1531
1532                        };
1533                }
1534        }
1535
1536        /**
1537         * Get owner thread name
1538         * For use sync DataSource
1539         * @return String
1540         * */
1541        public String getOwner() {
1542                return owner;
1543        }
1544
1545        /**
1546         * Get config properties
1547         * @return ConfigProperties
1548         * */
1549        public ConfigProperties getConfigProperties() {
1550                return configProperties;
1551        }
1552
1553        /**
1554         * Get sync data source list
1555         * @exception SQLException
1556         * @param owner - From master data source thread name
1557         * @return List<DriverDataSource>
1558         * */
1559        protected final synchronized List<DriverDataSource> getSyncDataSourceList(String owner) throws SQLException {
1560                if (syncDriverDataSource == null) {
1561                        syncDriverDataSource = new ArrayList<DriverDataSource>();
1562                        List<String> syncDsPropList = configProperties.getArray("SyncDataSource");
1563
1564                        if (syncDsPropList == null)
1565                                return null;
1566
1567                        for (String syncDsProp : syncDsPropList) {
1568
1569                                if (CommonTools.isBlank(syncDsProp))
1570                                        continue;
1571
1572                                File _appDsFile = new File(syncDsProp);
1573                                if (_appDsFile.exists()) {
1574                                        DriverDataSource dds = new DriverDataSource(_appDsFile);
1575                                        dds.owner = owner;
1576                                        syncDriverDataSource.add(dds);
1577                                } else {
1578                                        int errorCode = 99905;
1579                                        String errorMsg = String.format("SyncDatasource is null - %s .", syncDsProp);
1580                                        throw new SQLException(errorMsg) {
1581                                                @Override
1582                                                public int getErrorCode() {
1583                                                        return errorCode;
1584                                                }
1585                                        };
1586                                }
1587
1588                        }
1589                }
1590                return syncDriverDataSource;
1591        }
1592
1593        /**
1594         * For checksum master and sync data source connection url
1595         * Pause sync where same connection url
1596         * @return String
1597         * */
1598        public String getFingerprint() {
1599                return CommonTools.md5(String.format("%s-%s", this.url, this.username));
1600        }
1601        
1602        public synchronized double getAveUsage(){
1603            int size = aveUsageSum.size();
1604            double sum = 0.0D;
1605            for(int i = 0; i < size;i++){
1606                sum += aveUsageSum.get(i);
1607            }
1608            
1609            if(size <= 0) return 0.0D;
1610            
1611            return sum/size;
1612        }
1613        
1614        public void setMaxAveUsageSize(Integer maxAveUsageSize){
1615            this.maxAveUsageSize = maxAveUsageSize == null ? 10000 : maxAveUsageSize;
1616        }
1617
1618        private synchronized void countUsage(boolean calAve) {
1619                double usage = getUsage();
1620        if(calAve){
1621            if(aveUsageSum.size() >= maxAveUsageSize){
1622                        aveUsageSum.remove(0);
1623            }
1624            aveUsageSum.add(usage);
1625        }
1626                if (usage > maxUsage) {
1627                        maxUsage = usage;
1628                }
1629        }
1630
1631        /**
1632         * Get pool using 
1633         * @return double
1634         * */
1635        public synchronized double getUsage() {
1636                Map<Integer, Date> CONNECTION_USING_AT_MARK_SYNC = connectionPool.getConnectionUsingAtMark();
1637                int usingSize = CONNECTION_USING_AT_MARK_SYNC.keySet().size();
1638
1639                if (usingSize <= 0)
1640                        return 0.0D;
1641
1642                if (maxPoolSize <= 0)
1643                        return 0.0D;
1644                        
1645                return usingSize / (maxPoolSize * 1.0D);
1646        }
1647
1648        /** Abandon the following methods **/
1649
1650        @Override
1651        public java.lang.Object unwrap(java.lang.Class arg0) throws java.sql.SQLException {
1652                return null;
1653        }
1654
1655        @Override
1656        public boolean isWrapperFor(java.lang.Class arg0) throws java.sql.SQLException {
1657                return false;
1658        }
1659
1660        @Override
1661        public java.util.logging.Logger getParentLogger() throws java.sql.SQLFeatureNotSupportedException {
1662                return null;
1663        }
1664
1665        @Override
1666        public void setLogWriter(PrintWriter arg0) throws SQLException {
1667        }
1668
1669        @Override
1670        public PrintWriter getLogWriter() throws SQLException {
1671                return null;
1672        }
1673
1674        @Override
1675        public int getLoginTimeout() throws SQLException {
1676                return 0;
1677        }
1678
1679        @Override
1680        public void setLoginTimeout(int arg0) throws SQLException {
1681        }
1682
1683        @Override
1684        public Connection getConnection(String arg0, String arg1) throws SQLException {
1685                return null;
1686        }
1687
1688        @Override
1689        public ConnectionBuilder createConnectionBuilder() throws SQLException {
1690                return null;
1691        }
1692
1693}