source: Ballon/out/artifacts/geisa_artifact/WEB-INF/lib/mysql-connector-java-5.1.21/src/com/mysql/jdbc/LoadBalancingConnectionProxy.java @ 848

Last change on this file since 848 was 766, checked in by npipsl, 11 years ago
File size: 31.5 KB
Line 
1/*
2  Copyright (c) 2007, 2012, Oracle and/or its affiliates. All rights reserved.
3
4  The MySQL Connector/J is licensed under the terms of the GPLv2
5  <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most MySQL Connectors.
6  There are special exceptions to the terms and conditions of the GPLv2 as it is applied to
7  this software, see the FLOSS License Exception
8  <http://www.mysql.com/about/legal/licensing/foss-exception.html>.
9
10  This program is free software; you can redistribute it and/or modify it under the terms
11  of the GNU General Public License as published by the Free Software Foundation; version 2
12  of the License.
13
14  This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
15  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
16  See the GNU General Public License for more details.
17
18  You should have received a copy of the GNU General Public License along with this
19  program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth
20  Floor, Boston, MA 02110-1301  USA
21 
22 */
23package com.mysql.jdbc;
24
25import java.lang.reflect.Constructor;
26import java.lang.reflect.InvocationHandler;
27import java.lang.reflect.InvocationTargetException;
28import java.lang.reflect.Method;
29import java.lang.reflect.Proxy;
30import java.sql.SQLException;
31import java.util.ArrayList;
32import java.util.Collections;
33import java.util.HashMap;
34import java.util.Iterator;
35import java.util.LinkedList;
36import java.util.List;
37import java.util.Map;
38import java.util.Properties;
39import java.util.Set;
40
41/**
42 * An implementation of java.sql.Connection that load balances requests across a
43 * series of MySQL JDBC connections, where the balancing takes place at
44 * transaction commit.
45 *
46 * Therefore, for this to work (at all), you must use transactions, even if only
47 * reading data.
48 *
49 * This implementation will invalidate connections that it detects have had
50 * communication errors when processing a request. Problematic hosts will be
51 * added to a global blacklist for loadBalanceBlacklistTimeout ms, after which
52 * they will be removed from the blacklist and made eligible once again to be
53 * selected for new connections.
54 *
55 * This implementation is thread-safe, but it's questionable whether sharing a
56 * connection instance amongst threads is a good idea, given that transactions
57 * are scoped to connections in JDBC.
58 *
59 * @version $Id: $
60 *
61 */
62public class LoadBalancingConnectionProxy implements InvocationHandler,
63                PingTarget {
64
65        private static Method getLocalTimeMethod;
66       
67        private long totalPhysicalConnections = 0;
68        private long activePhysicalConnections = 0;
69        private String hostToRemove = null;
70        private long lastUsed = 0;
71        private long transactionCount = 0;
72        private ConnectionGroup connectionGroup = null;
73        private String closedReason = null;
74
75        public static final String BLACKLIST_TIMEOUT_PROPERTY_KEY = "loadBalanceBlacklistTimeout";
76
77        static {
78                try {
79                        getLocalTimeMethod = System.class.getMethod("nanoTime",
80                                        new Class[0]);
81                } catch (SecurityException e) {
82                        // ignore
83                } catch (NoSuchMethodException e) {
84                        // ignore
85                }
86        }
87
88        // Lifted from C/J 5.1's JDBC-2.0 connection pool classes, let's merge this
89        // if/when this gets into 5.1
90        protected class ConnectionErrorFiringInvocationHandler implements
91                        InvocationHandler {
92                Object invokeOn = null;
93
94                public ConnectionErrorFiringInvocationHandler(Object toInvokeOn) {
95                        invokeOn = toInvokeOn;
96                }
97
98                public Object invoke(Object proxy, Method method, Object[] args)
99                                throws Throwable {
100                        Object result = null;
101
102                        try {
103                                result = method.invoke(invokeOn, args);
104
105                                if (result != null) {
106                                        result = proxyIfInterfaceIsJdbc(result, result.getClass());
107                                }
108                        } catch (InvocationTargetException e) {
109                                dealWithInvocationException(e);
110                        }
111
112                        return result;
113                }
114        }
115
116        protected MySQLConnection currentConn;
117
118        protected List<String> hostList;
119
120        protected Map<String, ConnectionImpl> liveConnections;
121
122        private Map<ConnectionImpl, String> connectionsToHostsMap;
123
124        private long[] responseTimes;
125
126        private Map<String, Integer> hostsToListIndexMap;
127
128        private boolean inTransaction = false;
129
130        private long transactionStartTime = 0;
131
132        private Properties localProps;
133
134        private boolean isClosed = false;
135
136        private BalanceStrategy balancer;
137
138        private int retriesAllDown;
139
140        private static Map<String, Long> globalBlacklist = new HashMap<String, Long>();
141
142        private int globalBlacklistTimeout = 0;
143       
144        private long connectionGroupProxyID = 0;
145
146        private LoadBalanceExceptionChecker exceptionChecker;
147
148        private Map<Class<?>, Boolean> jdbcInterfacesForProxyCache = new HashMap<Class<?>, Boolean>();
149       
150        private MySQLConnection thisAsConnection = null;
151
152        private int autoCommitSwapThreshold = 0;
153       
154        private static Constructor<?> JDBC_4_LB_CONNECTION_CTOR;
155       
156        static {
157                if(Util.isJdbc4()){
158                        try {
159                                JDBC_4_LB_CONNECTION_CTOR =  Class.forName(
160                                                "com.mysql.jdbc.JDBC4LoadBalancedMySQLConnection").getConstructor(
161                                                new Class[] { LoadBalancingConnectionProxy.class});
162                        } catch (SecurityException e) {
163                                throw new RuntimeException(e);
164                        } catch (NoSuchMethodException e) {
165                                throw new RuntimeException(e);
166                        } catch (ClassNotFoundException e) {
167                                throw new RuntimeException(e);
168                        }
169                }
170        }
171       
172       
173       
174
175
176
177
178        /**
179         * Creates a proxy for java.sql.Connection that routes requests between the
180         * given list of host:port and uses the given properties when creating
181         * connections.
182         *
183         * @param hosts
184         * @param props
185         * @throws SQLException
186         */
187        LoadBalancingConnectionProxy(List<String> hosts, Properties props)
188                        throws SQLException {
189                String group = props.getProperty("loadBalanceConnectionGroup",
190                                null);
191                boolean enableJMX = false;
192                String enableJMXAsString = props.getProperty("loadBalanceEnableJMX",
193                                "false");
194                try{
195                        enableJMX = Boolean.parseBoolean(enableJMXAsString);
196                } catch (Exception e){
197                        throw SQLError.createSQLException(Messages.getString(
198                                        "LoadBalancingConnectionProxy.badValueForLoadBalanceEnableJMX",
199                                        new Object[] { enableJMXAsString }),
200                                        SQLError.SQL_STATE_ILLEGAL_ARGUMENT, null);                     
201                }
202               
203                if(group != null){
204                        this.connectionGroup = ConnectionGroupManager.getConnectionGroupInstance(group);
205                        if(enableJMX){
206                                ConnectionGroupManager.registerJmx();
207                        }
208                        this.connectionGroupProxyID = this.connectionGroup.registerConnectionProxy(this, hosts);
209                        hosts = new ArrayList<String>(this.connectionGroup.getInitialHosts());
210                }
211               
212                this.hostList = hosts;
213
214                int numHosts = this.hostList.size();
215
216                this.liveConnections = new HashMap<String, ConnectionImpl>(numHosts);
217                this.connectionsToHostsMap = new HashMap<ConnectionImpl, String>(numHosts);
218                this.responseTimes = new long[numHosts];
219                this.hostsToListIndexMap = new HashMap<String, Integer>(numHosts);
220
221                this.localProps = (Properties) props.clone();
222                this.localProps.remove(NonRegisteringDriver.HOST_PROPERTY_KEY);
223                this.localProps.remove(NonRegisteringDriver.PORT_PROPERTY_KEY);
224
225                for (int i = 0; i < numHosts; i++) {
226                        this.hostsToListIndexMap.put(this.hostList.get(i), Integer.valueOf(i));
227                        this.localProps.remove(NonRegisteringDriver.HOST_PROPERTY_KEY + "."
228                                        + (i + 1));
229                        this.localProps.remove(NonRegisteringDriver.PORT_PROPERTY_KEY + "."
230                                        + (i + 1));
231                }
232
233                this.localProps.remove(NonRegisteringDriver.NUM_HOSTS_PROPERTY_KEY);
234                this.localProps.setProperty("useLocalSessionState", "true");
235
236                String strategy = this.localProps.getProperty("loadBalanceStrategy",
237                                "random");
238               
239                String lbExceptionChecker = this.localProps.getProperty("loadBalanceExceptionChecker",
240                "com.mysql.jdbc.StandardLoadBalanceExceptionChecker");
241
242
243                String retriesAllDownAsString = this.localProps.getProperty(
244                                "retriesAllDown", "120");
245
246                try {
247                        this.retriesAllDown = Integer.parseInt(retriesAllDownAsString);
248                } catch (NumberFormatException nfe) {
249                        throw SQLError.createSQLException(Messages.getString(
250                                        "LoadBalancingConnectionProxy.badValueForRetriesAllDown",
251                                        new Object[] { retriesAllDownAsString }),
252                                        SQLError.SQL_STATE_ILLEGAL_ARGUMENT, null);
253                }
254                String blacklistTimeoutAsString = this.localProps.getProperty(
255                                BLACKLIST_TIMEOUT_PROPERTY_KEY, "0");
256
257                try {
258                        this.globalBlacklistTimeout = Integer
259                                        .parseInt(blacklistTimeoutAsString);
260                } catch (NumberFormatException nfe) {
261                        throw SQLError
262                                        .createSQLException(
263                                                        Messages
264                                                                        .getString(
265                                                                                        "LoadBalancingConnectionProxy.badValueForLoadBalanceBlacklistTimeout",
266                                                                                        new Object[] { retriesAllDownAsString }),
267                                                        SQLError.SQL_STATE_ILLEGAL_ARGUMENT, null);
268                }
269
270                if ("random".equals(strategy)) {
271                        this.balancer = (BalanceStrategy) Util.loadExtensions(null, props,
272                                        "com.mysql.jdbc.RandomBalanceStrategy",
273                                        "InvalidLoadBalanceStrategy", null).get(0);
274                } else if ("bestResponseTime".equals(strategy)) {
275                        this.balancer = (BalanceStrategy) Util.loadExtensions(null, props,
276                                        "com.mysql.jdbc.BestResponseTimeBalanceStrategy",
277                                        "InvalidLoadBalanceStrategy", null).get(0);
278                } else {
279                        this.balancer = (BalanceStrategy) Util.loadExtensions(null, props,
280                                        strategy, "InvalidLoadBalanceStrategy", null).get(0);
281                }
282
283                String autoCommitSwapThresholdAsString = props.getProperty("loadBalanceAutoCommitStatementThreshold",
284                "0");
285                try {
286                        this.autoCommitSwapThreshold  = Integer.parseInt(autoCommitSwapThresholdAsString);
287                } catch (NumberFormatException nfe) {
288                        throw SQLError.createSQLException(Messages.getString(
289                                        "LoadBalancingConnectionProxy.badValueForLoadBalanceAutoCommitStatementThreshold",
290                                        new Object[] { autoCommitSwapThresholdAsString }),
291                                        SQLError.SQL_STATE_ILLEGAL_ARGUMENT, null);
292                }
293               
294                String autoCommitSwapRegex = props.getProperty("loadBalanceAutoCommitStatementRegex","");
295                if(!("".equals(autoCommitSwapRegex))){
296                        try{
297                                "".matches(autoCommitSwapRegex);
298                        } catch (Exception e){
299                                throw SQLError.createSQLException(Messages.getString(
300                                                "LoadBalancingConnectionProxy.badValueForLoadBalanceAutoCommitStatementRegex",
301                                                new Object[] { autoCommitSwapRegex }),
302                                                SQLError.SQL_STATE_ILLEGAL_ARGUMENT, null);
303                        }
304                }
305               
306                if(this.autoCommitSwapThreshold > 0){
307                        String statementInterceptors = this.localProps.getProperty("statementInterceptors");
308                        if(statementInterceptors == null){
309                                this.localProps.setProperty("statementInterceptors", "com.mysql.jdbc.LoadBalancedAutoCommitInterceptor");
310                        } else if(statementInterceptors.length() > 0){
311                                this.localProps.setProperty("statementInterceptors", statementInterceptors + ",com.mysql.jdbc.LoadBalancedAutoCommitInterceptor");
312                        }
313                        props.setProperty("statementInterceptors", this.localProps.getProperty("statementInterceptors"));
314                       
315                }
316                this.balancer.init(null, props);
317               
318
319                this.exceptionChecker = (LoadBalanceExceptionChecker) Util.loadExtensions(null, props,
320                                lbExceptionChecker, "InvalidLoadBalanceExceptionChecker", null).get(0);
321                this.exceptionChecker.init(null, props);
322
323                if(Util.isJdbc4()  || JDBC_4_LB_CONNECTION_CTOR != null){
324                        thisAsConnection =  (MySQLConnection) Util.handleNewInstance(JDBC_4_LB_CONNECTION_CTOR,
325                                        new Object[] {this}, null);
326                }else{
327                        thisAsConnection = new LoadBalancedMySQLConnection(this);
328                }
329                pickNewConnection();
330               
331               
332        }
333
334        /**
335         * Creates a new physical connection for the given host:port and updates
336         * required internal mappings and statistics for that connection.
337         *
338         * @param hostPortSpec
339         * @return
340         * @throws SQLException
341         */
342        public synchronized ConnectionImpl createConnectionForHost(
343                        String hostPortSpec) throws SQLException {
344                Properties connProps = (Properties) this.localProps.clone();
345
346                String[] hostPortPair = NonRegisteringDriver
347                                .parseHostPortPair(hostPortSpec);
348                String hostName = hostPortPair[NonRegisteringDriver.HOST_NAME_INDEX];
349                String portNumber = hostPortPair[NonRegisteringDriver.PORT_NUMBER_INDEX];
350                String dbName = connProps
351                                .getProperty(NonRegisteringDriver.DBNAME_PROPERTY_KEY);
352
353                if (hostName == null) {
354                        throw new SQLException(
355                                        "Could not find a hostname to start a connection to");
356                }
357                if (portNumber == null) {
358                        portNumber = "3306";// use default
359                }
360
361                connProps.setProperty(NonRegisteringDriver.HOST_PROPERTY_KEY, hostName);
362                connProps.setProperty(NonRegisteringDriver.PORT_PROPERTY_KEY,
363                                portNumber);
364                connProps.setProperty(NonRegisteringDriver.HOST_PROPERTY_KEY + ".1",
365                                hostName);
366                connProps.setProperty(NonRegisteringDriver.PORT_PROPERTY_KEY + ".1",
367                                portNumber);
368                connProps.setProperty(NonRegisteringDriver.NUM_HOSTS_PROPERTY_KEY, "1");
369                connProps.setProperty("roundRobinLoadBalance", "false"); // make sure we
370                                                                                                                                        // don't
371                                                                                                                                        // pickup
372                                                                                                                                        // the
373                                                                                                                                        // default
374                                                                                                                                        // value
375
376                ConnectionImpl conn = (ConnectionImpl) ConnectionImpl.getInstance(
377                                hostName, Integer.parseInt(portNumber), connProps, dbName,
378                                "jdbc:mysql://" + hostName + ":" + portNumber + "/");
379               
380               
381
382                this.liveConnections.put(hostPortSpec, conn);
383                this.connectionsToHostsMap.put(conn, hostPortSpec);
384               
385
386                this.activePhysicalConnections++;
387                this.totalPhysicalConnections++;
388
389                conn.setProxy(this.thisAsConnection);
390
391                return conn;
392        }
393
394        /**
395         * @param e
396         * @throws SQLException
397         * @throws Throwable
398         * @throws InvocationTargetException
399         */
400        void dealWithInvocationException(InvocationTargetException e)
401                        throws SQLException, Throwable, InvocationTargetException {
402                Throwable t = e.getTargetException();
403
404                if (t != null) {
405                        if (t instanceof SQLException && shouldExceptionTriggerFailover((SQLException) t )) {
406                                invalidateCurrentConnection();
407                                pickNewConnection();
408                        }
409
410                        throw t;
411                }
412
413                throw e;
414        }
415
416        /**
417         * Closes current connection and removes it from required mappings.
418         *
419         * @throws SQLException
420         */
421        synchronized void invalidateCurrentConnection() throws SQLException {
422                try {
423                        if (!this.currentConn.isClosed()) {
424                                this.currentConn.close();
425                        }
426
427                } finally {
428                        // add host to the global blacklist, if enabled
429                        if (this.isGlobalBlacklistEnabled()) {
430                                this.addToGlobalBlacklist(this.connectionsToHostsMap
431                                                .get(this.currentConn));
432
433                        }
434                        // remove from liveConnections
435                        this.liveConnections.remove(this.connectionsToHostsMap
436                                        .get(this.currentConn));
437                        Object mappedHost = this.connectionsToHostsMap
438                                        .remove(this.currentConn);
439                        if (mappedHost != null
440                                        && this.hostsToListIndexMap.containsKey(mappedHost)) {
441                                int hostIndex = (this.hostsToListIndexMap
442                                                .get(mappedHost)).intValue();
443                                // reset the statistics for the host
444                                synchronized (this.responseTimes) {
445                                        this.responseTimes[hostIndex] = 0;
446                                }
447                        }
448                }
449        }
450
451        private void closeAllConnections() {
452                synchronized (this) {
453                        // close all underlying connections
454                        Iterator<ConnectionImpl> allConnections = this.liveConnections.values().iterator();
455
456                        while (allConnections.hasNext()) {
457                                try {
458                                        this.activePhysicalConnections--;
459                                        allConnections.next().close();
460                                } catch (SQLException e) {
461                                }
462                        }
463
464                        if (!this.isClosed) {
465                                this.balancer.destroy();
466                                if(this.connectionGroup != null){
467                                        this.connectionGroup.closeConnectionProxy(this);
468                                }
469                        }
470
471                        this.liveConnections.clear();
472                        this.connectionsToHostsMap.clear();
473                }
474
475        }
476       
477               
478        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
479                return this.invoke(proxy, method, args, true);
480        }
481
482        /**
483         * Proxies method invocation on the java.sql.Connection interface, trapping
484         * "close", "isClosed" and "commit/rollback" (to switch connections for load
485         * balancing).
486         *
487         * @param proxy
488         * @param method
489         * @param args
490         * @param swapAtTransactionBoundary
491         * @return
492         * @throws Throwable
493         */
494        public synchronized Object invoke(Object proxy, Method method, Object[] args, boolean swapAtTransactionBoundary)
495                        throws Throwable {
496
497                String methodName = method.getName();
498               
499                if("getLoadBalanceSafeProxy".equals(methodName)){
500                        return this.currentConn;
501                }
502
503                if ("equals".equals(methodName) && args.length == 1) {
504                        if (args[0] instanceof Proxy) {
505                                return Boolean.valueOf((((Proxy) args[0]).equals(this)));
506                        }
507                        return Boolean.valueOf(this.equals(args[0]));
508                }
509
510                if ("hashCode".equals(methodName)) {
511                        return Integer.valueOf(this.hashCode());
512                }
513
514                if ("close".equals(methodName)) {
515                        closeAllConnections();
516
517                        this.isClosed = true;
518                        this.closedReason = "Connection explicitly closed.";
519
520                        return null;
521                }
522
523                if ("isClosed".equals(methodName)) {
524                        return Boolean.valueOf(this.isClosed);
525                }
526
527                if (this.isClosed) {
528                        String reason = "No operations allowed after connection closed.";
529                        if(this.closedReason != null){
530                                reason += ("  " + this.closedReason);
531                        }
532                        throw SQLError.createSQLException(
533                                        reason,
534                                        SQLError.SQL_STATE_CONNECTION_NOT_OPEN, null /*
535                                                                                                                                 * no access to
536                                                                                                                                 * a interceptor
537                                                                                                                                 * here...
538                                                                                                                                 */);
539                }
540
541                if (!inTransaction) {
542                        this.inTransaction = true;
543                        this.transactionStartTime = getLocalTimeBestResolution();
544                        this.transactionCount++;
545                }
546
547                Object result = null;
548
549                try {
550                        this.lastUsed = System.currentTimeMillis();
551                        result = method.invoke(thisAsConnection, args);
552
553                        if (result != null) {
554                                if (result instanceof com.mysql.jdbc.Statement) {
555                                        ((com.mysql.jdbc.Statement) result).setPingTarget(this);
556                                }
557
558                                result = proxyIfInterfaceIsJdbc(result, result.getClass());
559                        }
560                } catch (InvocationTargetException e) {
561                        dealWithInvocationException(e);
562                } finally {
563                        if (swapAtTransactionBoundary && ("commit".equals(methodName) || "rollback".equals(methodName))) {
564                                this.inTransaction = false;
565
566                                // Update stats
567                                String host = this.connectionsToHostsMap.get(this.currentConn);
568                                // avoid NPE if the connection has already been removed from
569                                // connectionsToHostsMap
570                                // in invalidateCurrenctConnection()
571                                if (host != null) {
572                                        synchronized (this.responseTimes) {
573                                                int hostIndex = (this.hostsToListIndexMap
574                                                                .get(host)).intValue();
575
576                                                if(hostIndex < this.responseTimes.length){
577                                                        this.responseTimes[hostIndex] = getLocalTimeBestResolution()
578                                                                        - this.transactionStartTime;
579                                                }
580                                        }
581                                }
582                                pickNewConnection();
583                        }
584                }
585
586                return result;
587        }
588
589        /**
590         * Picks the "best" connection to use for the next transaction based on the
591         * BalanceStrategy in use.
592         *
593         * @throws SQLException
594         */
595        protected synchronized void pickNewConnection() throws SQLException {
596                if (this.currentConn == null) { // startup
597                        this.currentConn = this.balancer.pickConnection(this, Collections
598                                        .unmodifiableList(this.hostList), Collections
599                                        .unmodifiableMap(this.liveConnections),
600                                        this.responseTimes.clone(), this.retriesAllDown);
601                        return;
602                }
603               
604                if(this.currentConn.isClosed()){
605                        invalidateCurrentConnection();
606                }
607
608                int pingTimeout = this.currentConn.getLoadBalancePingTimeout();
609                boolean pingBeforeReturn = this.currentConn.getLoadBalanceValidateConnectionOnSwapServer();
610               
611                for(int hostsTried = 0, hostsToTry = this.hostList.size(); hostsTried <= hostsToTry; hostsTried++){
612                        try{
613                                ConnectionImpl newConn = this.balancer.pickConnection(
614
615                                        this, Collections.unmodifiableList(this.hostList), Collections
616                                                        .unmodifiableMap(this.liveConnections),
617                                        this.responseTimes.clone(), this.retriesAllDown);
618               
619                                if (this.currentConn != null) {
620                                        if(pingBeforeReturn){
621                                                if(pingTimeout == 0){
622                                                        newConn.ping();
623                                                } else {
624                                                        newConn.pingInternal(true, pingTimeout);
625                                                }
626                                        }
627                                       
628                                        syncSessionState(this.currentConn, newConn);
629                                }
630                               
631                                this.currentConn = newConn;
632                                return;
633                        } catch (SQLException e){
634                               
635                                if (shouldExceptionTriggerFailover(e)) {
636                                        // connection error, close up shop on current
637                                        // connection
638                                        invalidateCurrentConnection();
639                                }
640                        }
641
642                }
643                // no hosts available to swap connection to, close up.
644                this.isClosed = true;
645                this.closedReason = "Connection closed after inability to pick valid new connection during fail-over.";
646
647        }
648
649        /**
650         * Recursively checks for interfaces on the given object to determine if it
651         * implements a java.sql interface, and if so, proxies the instance so that
652         * we can catch and fire SQL errors.
653         *
654         * @param toProxy
655         * @param clazz
656         * @return
657         */
658        Object proxyIfInterfaceIsJdbc(Object toProxy, Class<?> clazz) {
659               
660                if(isInterfaceJdbc(clazz)){
661                       
662                Class<?>[] interfacesToProxy = getAllInterfacesToProxy(clazz);
663               
664                return Proxy.newProxyInstance(toProxy.getClass()
665                                .getClassLoader(), interfacesToProxy,
666                                createConnectionProxy(toProxy));
667                }
668
669                return toProxy;
670        }
671
672        private Map<Class<?>, Class<?>[]> allInterfacesToProxy = new HashMap<Class<?>, Class<?>[]>();
673       
674        private Class<?>[] getAllInterfacesToProxy(Class<?> clazz) {
675                Class<?>[] interfacesToProxy = this.allInterfacesToProxy.get(clazz);
676               
677                if (interfacesToProxy != null) {
678                        return interfacesToProxy;
679                }
680               
681                List<Class<?>> interfaces = new LinkedList<Class<?>>();
682               
683                Class<?> superClass = clazz;
684               
685                while (!(superClass.equals(Object.class))) {
686                        Class<?>[] declared = superClass.getInterfaces();
687                       
688                        for (int i = 0; i < declared.length; i++) {
689                                interfaces.add(declared[i]);
690                        }
691                       
692                        superClass = superClass.getSuperclass();
693                }
694               
695                interfacesToProxy = new Class[interfaces.size()];
696                interfaces.toArray(interfacesToProxy);
697               
698                this.allInterfacesToProxy.put(clazz, interfacesToProxy);
699               
700                return interfacesToProxy;
701        }
702       
703       
704        private boolean isInterfaceJdbc(Class<?> clazz){
705                if(this.jdbcInterfacesForProxyCache.containsKey(clazz)){
706                        return (this.jdbcInterfacesForProxyCache.get(clazz)).booleanValue();
707                }
708               
709                Class<?>[] interfaces = clazz.getInterfaces();
710
711                for (int i = 0; i < interfaces.length; i++) {
712                        String packageName = interfaces[i].getPackage().getName();
713
714                        if ("java.sql".equals(packageName)
715                                        || "javax.sql".equals(packageName)
716                                        || "com.mysql.jdbc".equals(packageName)) {
717                                this.jdbcInterfacesForProxyCache.put(clazz, Boolean.valueOf(true));
718                               
719                                return true;
720                        }
721
722                        if(isInterfaceJdbc(interfaces[i])){
723                                this.jdbcInterfacesForProxyCache.put(clazz, Boolean.valueOf(true));
724                               
725                                return true;
726                        }
727                }
728
729                this.jdbcInterfacesForProxyCache.put(clazz, Boolean.valueOf(false));
730                return false;
731               
732        }
733
734        protected ConnectionErrorFiringInvocationHandler createConnectionProxy(
735                        Object toProxy) {
736                return new ConnectionErrorFiringInvocationHandler(toProxy);
737        }
738
739        /**
740         * Returns best-resolution representation of local time, using nanoTime() if
741         * available, otherwise defaulting to currentTimeMillis().
742         */
743        private static long getLocalTimeBestResolution() {
744                if (getLocalTimeMethod != null) {
745                        try {
746                                return ((Long) getLocalTimeMethod.invoke(null, (Object[])null))
747                                                .longValue();
748                        } catch (IllegalArgumentException e) {
749                                // ignore - we fall through to currentTimeMillis()
750                        } catch (IllegalAccessException e) {
751                                // ignore - we fall through to currentTimeMillis()
752                        } catch (InvocationTargetException e) {
753                                // ignore - we fall through to currentTimeMillis()
754                        }
755                }
756
757                return System.currentTimeMillis();
758        }
759
760        public synchronized void doPing() throws SQLException {
761                SQLException se = null;
762                boolean foundHost = false;
763                int pingTimeout = this.currentConn.getLoadBalancePingTimeout();
764                synchronized (this) {
765                        for (Iterator<String> i = this.hostList.iterator(); i.hasNext();) {
766                                String host = i.next();
767                                ConnectionImpl conn = this.liveConnections.get(host);
768                                if (conn == null) {
769                                        continue;
770                                }
771                                try {
772                                        if(pingTimeout == 0){
773                                                conn.ping();
774                                        } else {
775                                                conn.pingInternal(true, pingTimeout);
776                                        }
777                                        foundHost = true;
778                                } catch (SQLException e) {
779                                        this.activePhysicalConnections--;
780                                        // give up if it is the current connection, otherwise NPE
781                                        // faking resultset later.
782                                        if (host.equals(this.connectionsToHostsMap
783                                                        .get(this.currentConn))) {
784                                                // clean up underlying connections, since connection
785                                                // pool won't do it
786                                                closeAllConnections();
787                                                this.isClosed = true;
788                                                this.closedReason = "Connection closed because ping of current connection failed.";
789                                                throw e;
790                                        }
791
792                                        // if the Exception is caused by ping connection lifetime
793                                        // checks, don't add to blacklist
794                                        if (e
795                                                        .getMessage()
796                                                        .equals(
797                                                                        Messages
798                                                                                        .getString("Connection.exceededConnectionLifetime"))) {
799                                                // only set the return Exception if it's null
800                                                if (se == null) {
801                                                        se = e;
802                                                }
803                                        } else {
804                                                // overwrite the return Exception no matter what
805                                                se = e;
806                                                if (this.isGlobalBlacklistEnabled()) {
807                                                        this.addToGlobalBlacklist(host);
808                                                }
809                                        }
810                                        // take the connection out of the liveConnections Map
811                                        this.liveConnections.remove(this.connectionsToHostsMap
812                                                        .get(conn));
813                                }
814                        }
815                }
816                // if there were no successful pings
817                if (!foundHost) {
818                        closeAllConnections();
819                        this.isClosed = true;
820                        this.closedReason = "Connection closed due to inability to ping any active connections.";
821                        // throw the stored Exception, if exists
822                        if (se != null) {
823                                throw se;
824                        }
825                        // or create a new SQLException and throw it, must be no
826                        // liveConnections
827                        ((ConnectionImpl) this.currentConn)
828                                        .throwConnectionClosedException();
829                }
830        }
831
832        public void addToGlobalBlacklist(String host, long timeout) {
833                if (this.isGlobalBlacklistEnabled()) {
834                        synchronized (globalBlacklist) {
835                                globalBlacklist.put(host, Long.valueOf(timeout));
836                        }
837                }
838        }
839       
840        public void addToGlobalBlacklist(String host){
841                addToGlobalBlacklist(host, System.currentTimeMillis()
842                                                + this.globalBlacklistTimeout);
843               
844        }
845
846        public boolean isGlobalBlacklistEnabled() {
847                return (this.globalBlacklistTimeout > 0);
848        }
849
850        public synchronized Map<String, Long> getGlobalBlacklist() {
851                if (!this.isGlobalBlacklistEnabled()) {
852                        String localHostToRemove = this.hostToRemove;
853                       
854                        if(hostToRemove != null){
855                                HashMap<String, Long> fakedBlacklist = new HashMap<String, Long>();
856                                fakedBlacklist.put(localHostToRemove, Long.valueOf(System.currentTimeMillis() + 5000));
857                                return fakedBlacklist;
858                        }
859                       
860                        return new HashMap<String, Long>(1);
861                }
862
863                // Make a local copy of the blacklist
864                Map<String, Long> blacklistClone = new HashMap<String, Long>(globalBlacklist.size());
865                // Copy everything from synchronized global blacklist to local copy for
866                // manipulation
867                synchronized (globalBlacklist) {
868                        blacklistClone.putAll(globalBlacklist);
869                }
870                Set<String> keys = blacklistClone.keySet();
871
872                // we're only interested in blacklisted hosts that are in the hostList
873                keys.retainAll(this.hostList);
874
875                // Don't need to synchronize here as we using a local copy
876                for (Iterator<String> i = keys.iterator(); i.hasNext();) {
877                        String host = i.next();
878                        // OK if null is returned because another thread already purged Map
879                        // entry.
880                        Long timeout = globalBlacklist.get(host);
881                        if (timeout != null
882                                        && timeout.longValue() < System.currentTimeMillis()) {
883                                // Timeout has expired, remove from blacklist
884                                synchronized (globalBlacklist) {
885                                        globalBlacklist.remove(host);
886                                }
887                                i.remove();
888                        }
889
890                }
891                if (keys.size() == this.hostList.size()) {
892                        // return an empty blacklist, let the BalanceStrategy
893                        // implementations try to connect to everything
894                        // since it appears that all hosts are unavailable - we don't want
895                        // to wait for
896                        // loadBalanceBlacklistTimeout to expire.
897                        return new HashMap<String, Long>(1);
898                }
899               
900                return blacklistClone;
901        }
902
903        public boolean shouldExceptionTriggerFailover(SQLException ex){
904                return this.exceptionChecker.shouldExceptionTriggerFailover(ex);
905               
906        }
907       
908        public void removeHostWhenNotInUse(String host)
909                        throws SQLException {
910                int timeBetweenChecks = 1000;
911                long timeBeforeHardFail = 15000;
912               
913                synchronized (this) {
914                        addToGlobalBlacklist(host, timeBeforeHardFail + 1000);
915                       
916                        long cur = System.currentTimeMillis();
917                       
918                        while (System.currentTimeMillis() - timeBeforeHardFail < cur) {
919       
920                                this.hostToRemove = host;
921                               
922                                if (!host.equals(this.currentConn.getHost())) {
923                                        removeHost(host);
924                                        return;
925                                }
926                        }
927                }
928               
929                try {
930                        Thread.sleep(timeBetweenChecks);
931                } catch (InterruptedException e) {
932                        // better to swallow this and retry.
933                }
934
935                removeHost(host);
936        }
937       
938        public synchronized void removeHost(String host) throws SQLException {
939
940                if (this.connectionGroup != null) {
941                        if (this.connectionGroup.getInitialHosts().size() == 1
942                                        && this.connectionGroup.getInitialHosts().contains(host)) {
943                                throw SQLError.createSQLException(
944                                                "Cannot remove only configured host.", null);
945                        }
946
947                        this.hostToRemove = host;
948                       
949                        if (host.equals(this.currentConn.getHost())) {
950                                closeAllConnections();
951                        } else {
952                                this.connectionsToHostsMap.remove(this.liveConnections
953                                                .remove(host));
954                                Integer idx = this.hostsToListIndexMap.remove(host);
955                                long[] newResponseTimes = new long[this.responseTimes.length - 1];
956                                int newIdx = 0;
957                                for (Iterator<String> i = this.hostList.iterator(); i.hasNext(); newIdx++) {
958                                        String copyHost = i.next();
959                                        if (idx != null
960                                                        && idx.intValue() < this.responseTimes.length) {
961                                                newResponseTimes[newIdx] = this.responseTimes[idx
962                                                                .intValue()];
963                                                this.hostsToListIndexMap.put(copyHost,
964                                                                Integer.valueOf(newIdx));
965                                        }
966                                }
967                                this.responseTimes = newResponseTimes;
968                        }
969                }
970
971        }
972
973        public synchronized boolean addHost(String host) {
974
975                if (this.hostsToListIndexMap.containsKey(host)) {
976                        return false;
977                }
978               
979                long[] newResponseTimes = new long[this.responseTimes.length + 1];
980               
981                for (int i = 0; i < this.responseTimes.length; i++) {
982                        newResponseTimes[i] = this.responseTimes[i];
983                }
984               
985                this.responseTimes = newResponseTimes;
986                this.hostList.add(host);
987                this.hostsToListIndexMap.put(host,
988                                Integer.valueOf(this.responseTimes.length - 1));
989               
990                return true;
991        }
992       
993        public synchronized long getLastUsed(){
994                return this.lastUsed;
995        }
996       
997        public synchronized boolean inTransaction(){
998                return this.inTransaction;
999        }
1000       
1001        public synchronized long getTransactionCount(){
1002                return this.transactionCount;
1003        }
1004       
1005        public synchronized long getActivePhysicalConnectionCount(){
1006                return this.activePhysicalConnections;
1007        }
1008       
1009        public synchronized long getTotalPhysicalConnectionCount(){
1010                return this.totalPhysicalConnections;
1011        }
1012       
1013        public synchronized long getConnectionGroupProxyID(){
1014                return this.connectionGroupProxyID;
1015        }
1016       
1017        public synchronized String getCurrentActiveHost() {
1018                MySQLConnection c = this.currentConn;
1019                if(c != null){
1020                        Object o = this.connectionsToHostsMap.get(c);
1021                        if(o != null){
1022                                return o.toString();
1023                        }
1024                }
1025                return null;
1026        }
1027       
1028        public synchronized long getCurrentTransactionDuration(){
1029
1030                if (this.inTransaction && this.transactionStartTime > 0) {
1031                        return getLocalTimeBestResolution() - this.transactionStartTime;
1032                }
1033               
1034                return 0;
1035        }
1036       
1037        protected void syncSessionState(Connection initial, Connection target)
1038                        throws SQLException {
1039                if (initial == null || target == null) {
1040                        return;
1041                }
1042                target.setAutoCommit(initial.getAutoCommit());
1043                target.setCatalog(initial.getCatalog());
1044                target.setTransactionIsolation(initial.getTransactionIsolation());
1045                target.setReadOnly(initial.isReadOnly());
1046
1047        }
1048
1049}
Note: See TracBrowser for help on using the repository browser.