/*
* HA-JDBC: High-Availability JDBC
* Copyright (C) 2012 Paul Ferraro
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.sf.hajdbc.sql;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.DriverPropertyInfo;
import java.sql.SQLException;
import java.time.Duration;
import java.util.AbstractMap;
import java.util.Map;
import java.util.Properties;
import java.util.SortedMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Function;
import java.util.regex.Pattern;
import net.sf.hajdbc.DatabaseCluster;
import net.sf.hajdbc.DatabaseClusterConfigurationFactory;
import net.sf.hajdbc.DatabaseClusterFactory;
import net.sf.hajdbc.invocation.InvocationStrategies;
import net.sf.hajdbc.invocation.Invoker;
import net.sf.hajdbc.logging.Level;
import net.sf.hajdbc.logging.Logger;
import net.sf.hajdbc.logging.LoggerFactory;
import net.sf.hajdbc.messages.Messages;
import net.sf.hajdbc.messages.MessagesFactory;
import net.sf.hajdbc.xml.XMLDatabaseClusterConfigurationFactory;
/**
* @author Paul Ferraro
*/
public class Driver extends AbstractDriver
{
private static final Pattern URL_PATTERN = Pattern.compile("jdbc:ha-jdbc:(?://)?([^/]+)(?:/.+)?");
private static final String CONFIG = "config";
private static final Messages messages = MessagesFactory.getMessages();
static final Logger logger = LoggerFactory.getLogger(Driver.class);
static volatile Duration timeout = Duration.ofSeconds(10);
static volatile DatabaseClusterFactory<java.sql.Driver, DriverDatabase> factory = new DatabaseClusterFactoryImpl<>();
static final Map<String, DatabaseClusterConfigurationFactory<java.sql.Driver, DriverDatabase>> configurationFactories = new ConcurrentHashMap<>();
static final ConcurrentMap<String, Map.Entry<DriverProxyFactory, java.sql.Driver>> proxies = new ConcurrentHashMap<>();
static
{
try
{
Driver driver = new Driver()
{
@Override
protected void finalize()
{
// When the driver instance that was registered with the DriverManager is finalized, close any clusters
for (String id : proxies.keySet())
{
try
{
close(id);
}
catch (Throwable e)
{
e.printStackTrace(DriverManager.getLogWriter());
}
}
}
};
DriverManager.registerDriver(driver);
}
catch (SQLException e)
{
logger.log(Level.ERROR, messages.registerDriverFailed(Driver.class), e);
}
}
public static void close(String id)
{
Map.Entry<DriverProxyFactory, java.sql.Driver> entry = proxies.remove(id);
if (entry != null)
{
DriverProxyFactory factory = entry.getKey();
factory.close();
factory.getDatabaseCluster().stop();
}
}
public static void setFactory(DatabaseClusterFactory<java.sql.Driver, DriverDatabase> factory)
{
Driver.factory = factory;
}
public static void setConfigurationFactory(String id, DatabaseClusterConfigurationFactory<java.sql.Driver, DriverDatabase> configurationFactory)
{
configurationFactories.put(id, configurationFactory);
}
/**
* {@inheritDoc}
* @see net.sf.hajdbc.sql.AbstractDriver#getUrlPattern()
*/
@Override
protected Pattern getUrlPattern()
{
return URL_PATTERN;
}
private static Map.Entry<DriverProxyFactory, java.sql.Driver> getProxyEntry(String id, Properties properties)
{
Function<String, Map.Entry<DriverProxyFactory, java.sql.Driver>> function = (String key) ->
{
DatabaseClusterConfigurationFactory<java.sql.Driver, DriverDatabase> configurationFactory = configurationFactories.get(key);
if (configurationFactory == null)
{
String config = (properties != null) ? properties.getProperty(CONFIG) : null;
configurationFactory = new XMLDatabaseClusterConfigurationFactory<>(id, config);
}
try
{
DatabaseCluster<java.sql.Driver, DriverDatabase> cluster = factory.createDatabaseCluster(key, configurationFactory, new DriverDatabaseClusterConfigurationBuilder());
cluster.start();
DriverProxyFactory factory = new DriverProxyFactory(cluster);
return new AbstractMap.SimpleImmutableEntry<>(factory, factory.createProxy());
}
catch (SQLException e)
{
throw new IllegalStateException(e);
}
};
return proxies.computeIfAbsent(id, function);
}
/**
* {@inheritDoc}
* @see java.sql.Driver#connect(java.lang.String, java.util.Properties)
*/
@Override
public Connection connect(String url, final Properties properties) throws SQLException
{
String id = this.parse(url);
// JDBC spec compliance
if (id == null) return null;
Map.Entry<DriverProxyFactory, java.sql.Driver> entry = getProxyEntry(id, properties);
TransactionContext<java.sql.Driver, DriverDatabase> context = new LocalTransactionContext<>(entry.getKey().getDatabaseCluster());
ConnectionProxyFactoryFactory<java.sql.Driver, DriverDatabase, java.sql.Driver> factory = new ConnectionProxyFactoryFactory<>(context);
DriverInvoker<Connection> invoker = (DriverDatabase database, java.sql.Driver driver) -> driver.connect(database.getLocation(), properties);
return factory.createProxyFactory(entry.getValue(), entry.getKey(), invoker, InvocationStrategies.INVOKE_ON_ALL.invoke(entry.getKey(), invoker)).createProxy();
}
/**
* {@inheritDoc}
* @see Driver#getPropertyInfo(java.lang.String, java.util.Properties)
*/
@Override
public DriverPropertyInfo[] getPropertyInfo(String url, final Properties properties) throws SQLException
{
String id = this.parse(url);
// JDBC spec compliance
if (id == null) return null;
Map.Entry<DriverProxyFactory, java.sql.Driver> entry = getProxyEntry(id, properties);
DriverInvoker<DriverPropertyInfo[]> invoker = (DriverDatabase database, java.sql.Driver driver) -> driver.getPropertyInfo(database.getLocation(), properties);
SortedMap<DriverDatabase, DriverPropertyInfo[]> results = InvocationStrategies.INVOKE_ON_ANY.invoke(entry.getKey(), invoker);
return results.get(results.firstKey());
}
/**
* @see java.sql.Driver#getParentLogger()
*/
@Override
public java.util.logging.Logger getParentLogger()
{
return java.util.logging.Logger.getGlobal();
}
private interface DriverInvoker<R> extends Invoker<java.sql.Driver, DriverDatabase, java.sql.Driver, R, SQLException>
{
}
}