All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.shesse.jdbcproxy.AlternatingConnectionFactory Maven / Gradle / Ivy

/**
 * (c) DICOS GmbH, 2011
 *
 * $Id$
 */

package com.shesse.jdbcproxy;

import java.net.URI;
import java.net.URISyntaxException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Timer;
import java.util.TimerTask;
import java.util.WeakHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.logging.Logger;

import org.h2.jdbcx.JdbcDataSource;

import com.shesse.jdbcproxy.ServerMonitor.Status;

/**
 * An instance of this class acts as a connection factory for
 * a HA pair of H2 database servers. It maintains DataSources
 * for both database servers and tries to connect to both of 
 * them.
 * 

* This class presumes that only one of the pair of servers * will accept database connections at any time. * * * @author sth */ public class AlternatingConnectionFactory { // ///////////////////////////////////////////////////////// // Class Members // ///////////////////////////////////////////////////////// /** */ private static Logger log = Logger.getLogger(AlternatingConnectionFactory.class.getName()); /** */ private String haUrl; /** */ private Properties info; /** */ private JdbcDataSource[] dataSources; /** */ private ServerMonitor[] monitors = null; /** * We keep track of the status of the monitors to avoid * race conditions which could arise when we ask the * monitor using getStatus() */ private ServerMonitor.Status[] monitorStatus = null; /** */ private volatile ServerMonitor activeMonitor; /** */ private Map activeConnections = new WeakHashMap(); /** */ private static ExecutorService threadPool = null; /** timer marked as daemon thread */ private static Timer timer = new Timer(true); /** */ private static Map factories = new HashMap(); // ///////////////////////////////////////////////////////// // Constructors // ///////////////////////////////////////////////////////// /** * @param info * @param haUrl */ private AlternatingConnectionFactory(String haUrl, Properties info, JdbcDataSource... dataSources) { this.haUrl = haUrl; this.info = info; this.dataSources = dataSources; log.fine("conn factory url="+haUrl); } // ///////////////////////////////////////////////////////// // Methods // ///////////////////////////////////////////////////////// /** * Return a connection factory for the given URL, username * and password. * @throws SQLException * */ public static synchronized AlternatingConnectionFactory getFactory(String url, Properties info) throws SQLException { AlternatingConnectionFactory factory = factories.get(getKey(url, info)); if (factory == null) { factory = AlternatingConnectionFactory.create(url, info); registerFactory(factory); } return factory; } /** * Returns a new database connection connected to the currently active * H2 instance. * * @throws SQLException * */ public HaConnection getConnection() throws SQLException { // There is a possible race condition where a // call to deregister() from another thread between // getActiveMonitor() and the the constructor call // my stop the monitor we are using. To prevent this we // temporarily register a dummy connection RegisteredHaConnection dc = new DummyRegisteredConnection(); register(dc); try { ServerMonitor m = getActiveMonitor(); return new HaConnection(this, m, m.createH2XaConnection()); } finally { deregister(dc); } } /** * Returns a new XA Connection connected to the currently active * H2 instance. * * @throws SQLException */ public HaXaConnection getXaConnection() throws SQLException { // There is a possible race condition where a // call to deregister() from another thread between // getActiveMonitor() and the the constructor call // my stop the monitor we are using. To prevent this we // temporarily register a dummy connection RegisteredHaConnection dc = new DummyRegisteredConnection(); register(dc); try { ServerMonitor m = getActiveMonitor(); return new HaXaConnection(this, m, m.createH2XaConnection()); } finally { deregister(dc); } } /** * @param haConnection */ public synchronized void register(RegisteredHaConnection haConnection) { log.finer("registering a connection: "+haConnection.getClass().getName()); activeConnections.put(haConnection, null); } /** * @param haConnection * @param monitoredBy */ public synchronized void deregister(RegisteredHaConnection haConnection) { log.finer("deregistering a connection: "+haConnection.getClass().getName()); activeConnections.remove(haConnection); if (activeConnections.isEmpty()) { log.fine("all connections are closed for "+haUrl+" - releasing monitors and factories"); // the last connection using this factory has // ended. We stop the monitors and deregister the factory stopMonitors(); deregisterFactory(this); } } /** * @param serverMonitor */ public static synchronized void submit(Runnable job) { getThreadPool().submit(job); } /** * @param timerTask * @param refreshcycle */ public static synchronized void schedule(TimerTask task, long delay) { getTimer().schedule(task, delay); } /** * @param serverMonitor * @param status */ public void updateOfMonitorStatus(ServerMonitor monitor, ServerMonitor.Status status) { log.fine("update monitor status: "+monitor+": "+status); ServerMonitor toBeCleaned = null; synchronized (this) { if (monitors == null) { return; } // remember new status for (int i = 0; i < monitors.length; i++) { if (monitors[i] == monitor) { monitorStatus[i] = status; } } // prepare cleanup of the old upon change of active moinitor if (status == Status.AVAILABLE || activeMonitor == null) { if (activeMonitor != monitor) { toBeCleaned = activeMonitor; activeMonitor = monitor; } } // wake up any instances waiting for an active monitor notifyAll(); } if (toBeCleaned != null) { cleanupForMonitor(toBeCleaned); } } /** * @param toBeCleaned */ private void cleanupForMonitor(ServerMonitor toBeCleaned) { log.fine("cleanup for "+toBeCleaned); // collect the connections and clean them up outside the synchronized // section because cleanup() may be complex and should not // be carried out with a lock being held. List cleanList = new ArrayList(); synchronized (this) { cleanList = new ArrayList(activeConnections.keySet()); } for (RegisteredHaConnection conn: cleanList) { if (conn != null && conn.getMonitoredBy() == toBeCleaned) { log.fine("cleaning "+conn.getClass().getName()+" to "+toBeCleaned); conn.cleanup(); } } } /** * @throws SQLException * */ private static AlternatingConnectionFactory create(String haUrl, Properties info) throws SQLException { URI[] haUris; try { haUris = splitUris(haUrl); } catch (URISyntaxException x) { throw new SQLException("cannot parse URL " + haUrl, x); } JdbcDataSource[] dataSources = new JdbcDataSource[haUris.length]; for (int i = 0; i < dataSources.length; i++) { String user = info.getProperty("user"); String password = info.getProperty("password"); String description = info.getProperty("description"); String loginTimeout = info.getProperty("loginTimeout"); JdbcDataSource ds = new JdbcDataSource(); ds.setURL(haUris[i].toString()); ds.setUser(user); ds.setPassword(password); if (description != null) { ds.setDescription(description); } if (loginTimeout != null) { try { ds.setLoginTimeout(Integer.parseInt(loginTimeout)); } catch (NumberFormatException x) { } } dataSources[i] = ds; } return new AlternatingConnectionFactory(haUrl, info, dataSources); } /** * @param value * @return * @throws URISyntaxException * @throws Exception */ private static URI[] splitUris(String value) throws URISyntaxException { URI uri = new URI(value); if (uri.getScheme() == null) { return new URI[] { uri }; } else if (uri.isOpaque()) { String scheme = uri.getScheme(); if ("h2ha".equals(scheme)) { scheme = "h2"; } URI[] subs = splitUris(uri.getSchemeSpecificPart()); for (int i = 0; i < subs.length; i++) { subs[i] = new URI(scheme, subs[i].toString(), uri.getFragment()); } return subs; } else { String auth = uri.getAuthority(); if (auth == null) { return new URI[] { uri }; } String[] authParts = auth.split(","); URI[] uris = new URI[authParts.length]; for (int i = 0; i < uris.length; i++) { uris[i] = new URI(uri.getScheme(), authParts[i], uri.getPath(), uri.getQuery(), uri.getFragment()); } return uris; } } /** * @param url * @param info * @return */ private static String getKey(String url, Properties info) { return url + "\n" + info.getProperty("user") + "\n" + info.getProperty("password"); } /** * @return */ private String getKey() { return getKey(haUrl, info); } /** * @param alternatingConnectionFactory */ private static synchronized void registerFactory(AlternatingConnectionFactory factory) { log.fine("registering factory "+factory); factories.put(factory.getKey(), factory); } /** * @param alternatingConnectionFactory */ private static synchronized void deregisterFactory(AlternatingConnectionFactory factory) { log.fine("deregistering factory "+factory); factories.remove(factory.getKey()); if (factories.isEmpty()) { log.fine("all factories are gone - cleaning up the threads"); cleanupThreads(); } } /** * */ private static synchronized ExecutorService getThreadPool() { // create thread pool with threads that are marked as daemon threads if (threadPool == null) { log.fine("creating thread pool"); threadPool = Executors.newCachedThreadPool(new ThreadFactory() { int nextThreadNo = 1; @Override public Thread newThread(Runnable r) { int n; synchronized (this) { n = nextThreadNo++; } Thread t = new Thread(r, "H2Ha-pool-"+n); t.setDaemon(true); return t; } }); } return threadPool; } /** * */ private static synchronized Timer getTimer() { // timer marked as daemon thread if (timer == null) { log.fine("creating timer"); timer = new Timer(true); } return timer; } /** * */ private static synchronized void cleanupThreads() { if (timer != null) { log.fine("releasing timer"); timer.cancel(); timer = null; } if (threadPool != null) { log.fine("releasing thread pool"); threadPool.shutdown(); threadPool = null; } } /** * @return */ private synchronized ServerMonitor getActiveMonitor() throws SQLException { startMonitors(); if (activeMonitor != null && activeMonitor.getStatus() != Status.AVAILABLE) { log.fine("active monitor is not available - scheduling immediate connect attempts"); for (ServerMonitor cmon: monitors) { cmon.checkAgain(); } } synchronized (this) { // we will wait at least until one of the monitors has // a result ... AVAILABLE or UNAVAILABLE log.fine("waiting for monitors"); while (activeMonitor == null) { try { log.fine("waiting because activeMonitor == null"); wait(); } catch (InterruptedException x) { throw new SQLException("unexpected interrupt", x); } } // we will wait we either have an available monitor or until // all monitors have reported 'unavailable' while (activeMonitor.getStatus() != ServerMonitor.Status.AVAILABLE) { boolean allHaveReported = true; for (ServerMonitor.Status status: monitorStatus) { if (status != Status.AVAILABLE && status != Status.UNAVAILABLE) { allHaveReported = false; break; } } if (allHaveReported) { break; } try { log.fine("waiting because activeMonitor is not AVAILABLE"); wait(); } catch (InterruptedException x) { throw new SQLException("unexpected interrupt", x); } } log.fine("found a monitor - "+activeMonitor+", status = "+activeMonitor.getStatus()); } return activeMonitor; } /** * */ private synchronized void startMonitors() { if (monitors == null) { log.fine("starting monitors"); monitors = new ServerMonitor[dataSources.length]; monitorStatus = new ServerMonitor.Status[dataSources.length]; for (int i = 0; i < monitors.length; i++) { monitors[i] = new ServerMonitor(this, dataSources[i]); } } } /** * */ private synchronized void stopMonitors() { if (monitors != null) { log.fine("stopping monitors"); for (ServerMonitor monitor: monitors) { if (monitor != null) { monitor.stop(); } } monitors = null; monitorStatus = null; activeMonitor = null; } } /** * */ public String toString() { return haUrl; } // ///////////////////////////////////////////////////////// // Inner Classes // ///////////////////////////////////////////////////////// /** * */ public interface RegisteredHaConnection { public ServerMonitor getMonitoredBy(); public void cleanup(); } /** */ private static class DummyRegisteredConnection implements RegisteredHaConnection { @Override public ServerMonitor getMonitoredBy() { return null; } @Override public void cleanup() { } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy