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