diff --git a/src/main/java/com/alibaba/druid/pool/ha/DataSourceCreator.java b/src/main/java/com/alibaba/druid/pool/ha/DataSourceCreator.java index 90a4206fa6..ea9b9b3203 100644 --- a/src/main/java/com/alibaba/druid/pool/ha/DataSourceCreator.java +++ b/src/main/java/com/alibaba/druid/pool/ha/DataSourceCreator.java @@ -16,16 +16,8 @@ package com.alibaba.druid.pool.ha; import java.sql.SQLException; -import java.util.List; -import java.util.Map; -import java.util.Properties; -import java.util.concurrent.ConcurrentHashMap; - -import javax.sql.DataSource; import com.alibaba.druid.pool.DruidDataSource; -import com.alibaba.druid.support.logging.Log; -import com.alibaba.druid.support.logging.LogFactory; /** * An utility class to create DruidDataSource dynamically. @@ -33,51 +25,6 @@ import com.alibaba.druid.support.logging.LogFactory; * @author DigitalSonic */ public class DataSourceCreator { - private final static Log LOG = LogFactory.getLog(DataSourceCreator.class); - - private Properties properties; - private List nameList; - - public DataSourceCreator(String file) { - this(file, ""); - } - - public DataSourceCreator(String file, String propertyPrefix) { - this.properties = PropertiesUtils.loadProperties(file); - this.nameList = PropertiesUtils.loadNameList(this.properties, propertyPrefix); - } - - public Map createMap(HighAvailableDataSource haDataSource) { - Map map = new ConcurrentHashMap(); - - if (nameList == null || nameList.isEmpty()) { - LOG.error("No DataSource will be created!"); - return map; - } - - for (String n : nameList) { - String url = properties.getProperty(n + ".url"); - String username = properties.getProperty(n + ".username"); - String password = properties.getProperty(n + ".password"); - LOG.info("Creating " + n + " with url[" + url + "] and username[" + username + "]."); - DruidDataSource dataSource = null; - try { - dataSource = create(n, url, username, password, haDataSource); - map.put(n, dataSource); - } catch(Exception e) { - LOG.error("Can NOT create DruidDataSource for " + n, e); - if (dataSource != null) { - try { - dataSource.close(); - } catch (Exception ex) { - LOG.error("Exception occurred while closing the FAILURE DataSource.", ex); - } - } - } - } - LOG.info(map.size() + " DruidDataSource(s) created. "); - return map; - } public static DruidDataSource create(String name, String url, String username, String password, HighAvailableDataSource haDataSource) throws SQLException { @@ -129,8 +76,4 @@ public class DataSourceCreator { return dataSource; } - - public List getNameList() { - return nameList; - } } diff --git a/src/main/java/com/alibaba/druid/pool/ha/HighAvailableDataSource.java b/src/main/java/com/alibaba/druid/pool/ha/HighAvailableDataSource.java index 4cb1a710a6..2adfedfcb9 100644 --- a/src/main/java/com/alibaba/druid/pool/ha/HighAvailableDataSource.java +++ b/src/main/java/com/alibaba/druid/pool/ha/HighAvailableDataSource.java @@ -35,6 +35,9 @@ import com.alibaba.druid.filter.Filter; import com.alibaba.druid.pool.DruidAbstractDataSource; import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.druid.pool.WrapperAdapter; +import com.alibaba.druid.pool.ha.node.FileNodeListener; +import com.alibaba.druid.pool.ha.node.NodeListener; +import com.alibaba.druid.pool.ha.node.PoolUpdater; import com.alibaba.druid.pool.ha.selector.DataSourceSelector; import com.alibaba.druid.pool.ha.selector.DataSourceSelectorEnum; import com.alibaba.druid.pool.ha.selector.DataSourceSelectorFactory; @@ -96,6 +99,9 @@ public class HighAvailableDataSource extends WrapperAdapter implements DataSourc private volatile boolean inited = false; + private PoolUpdater poolUpdater = new PoolUpdater(this); + private NodeListener nodeListener; + public void init() { if (inited) { return; @@ -105,7 +111,7 @@ public class HighAvailableDataSource extends WrapperAdapter implements DataSourc return; } if (dataSourceMap == null || dataSourceMap.isEmpty()) { - dataSourceMap = new DataSourceCreator(dataSourceFile, propertyPrefix).createMap(this); + createNodeMap(); } if (selector == null) { setSelector(DataSourceSelectorEnum.RANDOM.getName()); @@ -122,6 +128,12 @@ public class HighAvailableDataSource extends WrapperAdapter implements DataSourc } public void destroy() { + if (nodeListener != null) { + nodeListener.destroy(); + } + if (poolUpdater != null) { + poolUpdater.destroy(); + } if (dataSourceMap == null || dataSourceMap.isEmpty()) { return; } @@ -211,29 +223,11 @@ public class HighAvailableDataSource extends WrapperAdapter implements DataSourc return selector == null ? null : selector.getName(); } - public DataSourceSelector getDataSourceSelector() { - return this.selector; - } - - public void setDataSourceSelector(DataSourceSelector dataSourceSelector) { - this.selector = dataSourceSelector; - } - @Override public Connection getConnection(String username, String password) throws SQLException { throw new UnsupportedOperationException("Not supported by HighAvailableDataSource."); } - @Override - public PrintWriter getLogWriter() throws SQLException { - return logWriter; - } - - @Override - public void setLogWriter(PrintWriter out) throws SQLException { - this.logWriter = out; - } - @Override public void setLoginTimeout(int seconds) { DriverManager.setLoginTimeout(seconds); @@ -248,16 +242,34 @@ public class HighAvailableDataSource extends WrapperAdapter implements DataSourc throw new SQLFeatureNotSupportedException(); } - public String getDriverClassName() { - return driverClassName; - } + public void setConnectionProperties(String connectionProperties) { + this.connectionProperties = connectionProperties; - public void setDriverClassName(String driverClassName) { - this.driverClassName = driverClassName; - } + // COPIED FROM DruidAbstractDataSource.setConnectionProperties() - public Properties getConnectProperties() { - return connectProperties; + if (connectionProperties == null || connectionProperties.trim().length() == 0) { + setConnectProperties(null); + return; + } + + String[] entries = connectionProperties.split(";"); + Properties properties = new Properties(); + for (int i = 0; i < entries.length; i++) { + String entry = entries[i]; + if (entry.length() > 0) { + int index = entry.indexOf('='); + if (index > 0) { + String name = entry.substring(0, index); + String value = entry.substring(index + 1); + properties.setProperty(name, value); + } else { + // no value is empty string which is how java.util.Properties works + properties.setProperty(entry, ""); + } + } + } + + setConnectProperties(properties); } public void setConnectProperties(Properties connectProperties) { @@ -272,6 +284,68 @@ public class HighAvailableDataSource extends WrapperAdapter implements DataSourc } } + private void createNodeMap() { + if (nodeListener == null) { + // Compatiable with the old version. + // Create a FileNodeListener to watch the dataSourceFile. + FileNodeListener listener = new FileNodeListener(); + listener.setFile(dataSourceFile); + listener.setPrefix(propertyPrefix); + listener.setObserver(poolUpdater); + listener.init(); + nodeListener = listener; + } + nodeListener.update(); // Do update in the current Thread at the startup + } + + // Getters & Setters + + public PoolUpdater getPoolUpdater() { + return poolUpdater; + } + + public void setPoolUpdater(PoolUpdater poolUpdater) { + this.poolUpdater = poolUpdater; + } + + public NodeListener getNodeListener() { + return nodeListener; + } + + public void setNodeListener(NodeListener nodeListener) { + this.nodeListener = nodeListener; + } + + public DataSourceSelector getDataSourceSelector() { + return this.selector; + } + + public void setDataSourceSelector(DataSourceSelector dataSourceSelector) { + this.selector = dataSourceSelector; + } + + @Override + public PrintWriter getLogWriter() throws SQLException { + return logWriter; + } + + @Override + public void setLogWriter(PrintWriter out) throws SQLException { + this.logWriter = out; + } + + public String getDriverClassName() { + return driverClassName; + } + + public void setDriverClassName(String driverClassName) { + this.driverClassName = driverClassName; + } + + public Properties getConnectProperties() { + return connectProperties; + } + public int getInitialSize() { return initialSize; } @@ -452,36 +526,6 @@ public class HighAvailableDataSource extends WrapperAdapter implements DataSourc return connectionProperties; } - public void setConnectionProperties(String connectionProperties) { - this.connectionProperties = connectionProperties; - - // COPIED FROM DruidAbstractDataSource.setConnectionProperties() - - if (connectionProperties == null || connectionProperties.trim().length() == 0) { - setConnectProperties(null); - return; - } - - String[] entries = connectionProperties.split(";"); - Properties properties = new Properties(); - for (int i = 0; i < entries.length; i++) { - String entry = entries[i]; - if (entry.length() > 0) { - int index = entry.indexOf('='); - if (index > 0) { - String name = entry.substring(0, index); - String value = entry.substring(index + 1); - properties.setProperty(name, value); - } else { - // no value is empty string which is how java.util.Properties works - properties.setProperty(entry, ""); - } - } - } - - setConnectProperties(properties); - } - public String getFilters() { return filters; } diff --git a/src/main/java/com/alibaba/druid/pool/ha/node/FileNodeListener.java b/src/main/java/com/alibaba/druid/pool/ha/node/FileNodeListener.java index 6ed37cd560..6005135d49 100644 --- a/src/main/java/com/alibaba/druid/pool/ha/node/FileNodeListener.java +++ b/src/main/java/com/alibaba/druid/pool/ha/node/FileNodeListener.java @@ -60,7 +60,7 @@ public class FileNodeListener extends NodeListener { LOG.error("Can NOT update the node list.", e); } } - }, 0, intervalSeconds, TimeUnit.SECONDS); + }, intervalSeconds, intervalSeconds, TimeUnit.SECONDS); } /** @@ -77,14 +77,22 @@ public class FileNodeListener extends NodeListener { String url = originalProperties.getProperty(n + ".url"); String username = originalProperties.getProperty(n + ".username"); String password = originalProperties.getProperty(n + ".password"); - if (url == null || url.isEmpty() - || username == null || username.isEmpty() - || password == null || password.isEmpty()) { + if (url == null || url.isEmpty()) { + LOG.warn(n + ".url is EMPTY! IGNORE!"); continue; + } else { + properties.setProperty(n + ".url", url); + } + if (username == null || username.isEmpty()) { + LOG.warn(n + ".username is EMPTY. Maybe you should check the config."); + } else { + properties.setProperty(n + ".username", username); + } + if (password == null || password.isEmpty()) { + LOG.warn(n + ".password is EMPTY. Maybe you should check the config."); + } else { + properties.setProperty(n + ".password", password); } - properties.setProperty(n + ".url", url); - properties.setProperty(n + ".username", username); - properties.setProperty(n + ".password", password); } List events = NodeEvent.getEventListFromProperties(getProperties(), properties); @@ -94,6 +102,18 @@ public class FileNodeListener extends NodeListener { return events; } + @Override + public void destroy() { + if (executor == null || executor.isShutdown()) { + return; + } + try { + executor.shutdown(); + } catch (Exception e) { + LOG.error("Can NOT shutdown the ScheduledExecutorService.", e); + } + } + public int getIntervalSeconds() { return intervalSeconds; } diff --git a/src/main/java/com/alibaba/druid/pool/ha/node/NodeListener.java b/src/main/java/com/alibaba/druid/pool/ha/node/NodeListener.java index 4f61bef40b..f91b90c056 100644 --- a/src/main/java/com/alibaba/druid/pool/ha/node/NodeListener.java +++ b/src/main/java/com/alibaba/druid/pool/ha/node/NodeListener.java @@ -15,11 +15,16 @@ */ package com.alibaba.druid.pool.ha.node; +import com.alibaba.druid.support.logging.Log; +import com.alibaba.druid.support.logging.LogFactory; + import java.util.Date; import java.util.List; import java.util.Observable; import java.util.Observer; import java.util.Properties; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; /** * This abstract class defines a listener to monitor the change of DataSource nodes. @@ -28,15 +33,23 @@ import java.util.Properties; * @see Observable */ public abstract class NodeListener extends Observable { + private final static Log LOG = LogFactory.getLog(NodeListener.class); + private Properties properties = new Properties(); private Date lastUpdateTime = null; private Observer observer = null; + private Lock lock = new ReentrantLock(); /** * The method implements the detail update logic. */ public abstract List refresh(); + /** + * Do some cleanup. + */ + public abstract void destroy(); + /** * Add the given PoolUpdater as the Observer. * @@ -55,15 +68,23 @@ public abstract class NodeListener extends Observable { * @see #refresh() */ public void update() { - List events = refresh(); - if (events != null && !events.isEmpty()) { - this.lastUpdateTime = new Date(); - NodeEvent[] arr = new NodeEvent[events.size()]; - for (int i = 0; i < events.size(); i++) { - arr[i] = events.get(i); + if (!lock.tryLock()) { + LOG.info("Can not acquire the lock, skip this time."); + return; + } + try { + List events = refresh(); + if (events != null && !events.isEmpty()) { + this.lastUpdateTime = new Date(); + NodeEvent[] arr = new NodeEvent[events.size()]; + for (int i = 0; i < events.size(); i++) { + arr[i] = events.get(i); + } + this.setChanged(); + this.notifyObservers(arr); } - this.setChanged(); - this.notifyObservers(arr); + } finally { + lock.unlock(); } } diff --git a/src/main/java/com/alibaba/druid/pool/ha/node/PoolUpdater.java b/src/main/java/com/alibaba/druid/pool/ha/node/PoolUpdater.java index 80c79670ea..7977bac637 100644 --- a/src/main/java/com/alibaba/druid/pool/ha/node/PoolUpdater.java +++ b/src/main/java/com/alibaba/druid/pool/ha/node/PoolUpdater.java @@ -48,6 +48,9 @@ public class PoolUpdater implements Observer { private Lock lock = new ReentrantLock(); private ScheduledExecutorService executor; + /** + * Create a ScheduledExecutorService to remove unused DataSources. + */ public PoolUpdater() { executor = Executors.newScheduledThreadPool(1); executor.scheduleAtFixedRate(new Runnable() { @@ -62,11 +65,28 @@ public class PoolUpdater implements Observer { }, 60, 60, TimeUnit.SECONDS); } + /** + * @see #PoolUpdater() + */ public PoolUpdater(HighAvailableDataSource highAvailableDataSource) { this(); setHighAvailableDataSource(highAvailableDataSource); } + public void destroy() { + if (executor == null || executor.isShutdown()) { + return; + } + try { + executor.shutdown(); + } catch (Exception e) { + LOG.error("Can NOT shutdown the ScheduledExecutorService.", e); + } + } + + /** + * Process the given NodeEvent[]. Maybe add / delete some nodes. + */ @Override public void update(Observable o, Object arg) { if (!(o instanceof NodeListener)) { @@ -99,6 +119,9 @@ public class PoolUpdater implements Observer { } } + /** + * Remove unused DataSources. + */ public void removeDataSources() { if (nodesToDel == null || nodesToDel.isEmpty()) { return; @@ -157,12 +180,20 @@ public class PoolUpdater implements Observer { cancelBlacklistNode(nodeName); return; } + DruidDataSource dataSource = null; try { - DruidDataSource dataSource = DataSourceCreator.create(nodeName, url, username, + dataSource = DataSourceCreator.create(nodeName, url, username, password, this.highAvailableDataSource); map.put(nodeName, dataSource); } catch (Exception e) { - LOG.error("Can NOT create DataSource " + nodeName, e); + LOG.error("Can NOT create DataSource " + nodeName + ". IGNORE IT.", e); + if (dataSource != null) { + try { + dataSource.close(); + } catch (Exception ex) { + LOG.error("Exception occurred while closing the FAILURE DataSource.", ex); + } + } } } diff --git a/src/test/java/com/alibaba/druid/pool/ha/DataSourceCreatorTest.java b/src/test/java/com/alibaba/druid/pool/ha/DataSourceCreatorTest.java deleted file mode 100644 index 76a4dfc08d..0000000000 --- a/src/test/java/com/alibaba/druid/pool/ha/DataSourceCreatorTest.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.alibaba.druid.pool.ha; - -import org.junit.Test; - -import javax.sql.DataSource; -import java.util.List; -import java.util.Map; - -import static org.junit.Assert.*; - -public class DataSourceCreatorTest { - @Test - public void testNameList() { - String file = "/com/alibaba/druid/pool/ha/ha-with-prefix-datasource.properties"; - DataSourceCreator creator = new DataSourceCreator(file, "prefix1"); - List list = creator.getNameList(); - assertFalse(list.isEmpty()); - assertEquals(1, list.size()); - assertEquals("prefix1.foo", list.get(0)); - - creator = new DataSourceCreator(file); - list = creator.getNameList(); - assertEquals(3, list.size()); - for (String k : list) { - assertNotEquals("bar", k); - } - } - - @Test - public void createMap() throws Exception { - HighAvailableDataSource highAvailableDataSource = new HighAvailableDataSource(); - String file = "/com/alibaba/druid/pool/ha/ha-datasource.properties"; - DataSourceCreator creator = new DataSourceCreator(file); - Map map = creator.createMap(highAvailableDataSource); - assertEquals(2, map.size()); - assertNotNull(map.get("foo")); - assertNotNull(map.get("bar")); - } - - @Test - public void createMapWithPrefix() throws Exception { - HighAvailableDataSource highAvailableDataSource = new HighAvailableDataSource(); - String file = "/com/alibaba/druid/pool/ha/ha-with-prefix-datasource.properties"; - DataSourceCreator creator = new DataSourceCreator(file, "prefix1"); - Map map = creator.createMap(highAvailableDataSource); - assertEquals(1, map.size()); - assertNotNull(map.get("prefix1.foo")); - assertNull(map.get("prefix2.foo")); - - creator = new DataSourceCreator(file, "prefix2"); - map = creator.createMap(highAvailableDataSource); - assertEquals(1, map.size()); - assertNull(map.get("prefix1.foo")); - assertNotNull(map.get("prefix2.foo")); - } - -} \ No newline at end of file diff --git a/src/test/java/com/alibaba/druid/pool/ha/selector/BaseRandomDataSourceSelectorTest.java b/src/test/java/com/alibaba/druid/pool/ha/selector/BaseRandomDataSourceSelectorTest.java index 78a6ae9e38..4b5cddbf3d 100644 --- a/src/test/java/com/alibaba/druid/pool/ha/selector/BaseRandomDataSourceSelectorTest.java +++ b/src/test/java/com/alibaba/druid/pool/ha/selector/BaseRandomDataSourceSelectorTest.java @@ -26,6 +26,7 @@ public class BaseRandomDataSourceSelectorTest { @After public void tearDown() { dataSourceMap = null; + dataSource.destroy(); dataSource = null; } } diff --git a/src/test/java/com/alibaba/druid/pool/ha/selector/NamedDataSourceSelectorTest.java b/src/test/java/com/alibaba/druid/pool/ha/selector/NamedDataSourceSelectorTest.java index 41e552de1a..6052ad4098 100644 --- a/src/test/java/com/alibaba/druid/pool/ha/selector/NamedDataSourceSelectorTest.java +++ b/src/test/java/com/alibaba/druid/pool/ha/selector/NamedDataSourceSelectorTest.java @@ -29,6 +29,7 @@ public class NamedDataSourceSelectorTest { @After public void tearDown() { dataSourceMap = null; + dataSource.destroy(); dataSource = null; } diff --git a/src/test/java/com/alibaba/druid/pool/ha/selector/RandomDataSourceSelectorWithValidationTest.java b/src/test/java/com/alibaba/druid/pool/ha/selector/RandomDataSourceSelectorWithValidationTest.java index 4059600723..9367973879 100644 --- a/src/test/java/com/alibaba/druid/pool/ha/selector/RandomDataSourceSelectorWithValidationTest.java +++ b/src/test/java/com/alibaba/druid/pool/ha/selector/RandomDataSourceSelectorWithValidationTest.java @@ -25,6 +25,7 @@ public class RandomDataSourceSelectorWithValidationTest { @After public void tearDown() { + highAvailableDataSource.destroy(); highAvailableDataSource = null; }