001/* 002 * HA-JDBC: High-Availability JDBC 003 * Copyright (C) 2012 Paul Ferraro 004 * 005 * This program is free software: you can redistribute it and/or modify 006 * it under the terms of the GNU Lesser General Public License as published by 007 * the Free Software Foundation, either version 3 of the License, or 008 * (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 013 * GNU Lesser General Public License for more details. 014 * 015 * You should have received a copy of the GNU Lesser General Public License 016 * along with this program. If not, see <http://www.gnu.org/licenses/>. 017 */ 018package net.sf.hajdbc.sql; 019 020import java.sql.Connection; 021import java.sql.DriverManager; 022import java.sql.DriverPropertyInfo; 023import java.sql.SQLException; 024import java.util.Map; 025import java.util.Properties; 026import java.util.SortedMap; 027import java.util.concurrent.ConcurrentHashMap; 028import java.util.concurrent.TimeUnit; 029import java.util.regex.Pattern; 030 031import net.sf.hajdbc.AbstractDriver; 032import net.sf.hajdbc.DatabaseCluster; 033import net.sf.hajdbc.DatabaseClusterConfigurationFactory; 034import net.sf.hajdbc.DatabaseClusterFactory; 035import net.sf.hajdbc.ExceptionType; 036import net.sf.hajdbc.Messages; 037import net.sf.hajdbc.invocation.InvocationStrategies; 038import net.sf.hajdbc.invocation.Invoker; 039import net.sf.hajdbc.logging.Level; 040import net.sf.hajdbc.logging.Logger; 041import net.sf.hajdbc.logging.LoggerFactory; 042import net.sf.hajdbc.util.TimePeriod; 043import net.sf.hajdbc.util.concurrent.MapRegistryStoreFactory; 044import net.sf.hajdbc.util.concurrent.LifecycleRegistry; 045import net.sf.hajdbc.util.concurrent.Registry; 046import net.sf.hajdbc.xml.XMLDatabaseClusterConfigurationFactory; 047 048/** 049 * @author Paul Ferraro 050 */ 051public final class Driver extends AbstractDriver 052{ 053 private static final Pattern URL_PATTERN = Pattern.compile("jdbc:ha-jdbc:(?://)?([^/]+)(?:/.+)?"); 054 private static final String CONFIG = "config"; 055 private static final Logger logger = LoggerFactory.getLogger(Driver.class); 056 057 static volatile TimePeriod timeout = new TimePeriod(10, TimeUnit.SECONDS); 058 static volatile DatabaseClusterFactory<java.sql.Driver, DriverDatabase> factory = new DatabaseClusterFactoryImpl<java.sql.Driver, DriverDatabase>(); 059 060 static final Map<String, DatabaseClusterConfigurationFactory<java.sql.Driver, DriverDatabase>> configurationFactories = new ConcurrentHashMap<String, DatabaseClusterConfigurationFactory<java.sql.Driver, DriverDatabase>>(); 061 private static final Registry.Factory<String, DatabaseCluster<java.sql.Driver, DriverDatabase>, Properties, SQLException> registryFactory = new Registry.Factory<String, DatabaseCluster<java.sql.Driver, DriverDatabase>, Properties, SQLException>() 062 { 063 @Override 064 public DatabaseCluster<java.sql.Driver, DriverDatabase> create(String id, Properties properties) throws SQLException 065 { 066 DatabaseClusterConfigurationFactory<java.sql.Driver, DriverDatabase> configurationFactory = configurationFactories.get(id); 067 068 if (configurationFactory == null) 069 { 070 String config = (properties != null) ? properties.getProperty(CONFIG) : null; 071 configurationFactory = new XMLDatabaseClusterConfigurationFactory<java.sql.Driver, DriverDatabase>(DriverDatabaseClusterConfiguration.class, id, config); 072 } 073 074 return factory.createDatabaseCluster(id, configurationFactory); 075 } 076 077 @Override 078 public TimePeriod getTimeout() 079 { 080 return timeout; 081 } 082 }; 083 private static final Registry<String, DatabaseCluster<java.sql.Driver, DriverDatabase>, Properties, SQLException> registry = new LifecycleRegistry<String, DatabaseCluster<java.sql.Driver, DriverDatabase>, Properties, SQLException>(registryFactory, new MapRegistryStoreFactory<String>(), ExceptionType.SQL.<SQLException>getExceptionFactory()); 084 085 static 086 { 087 try 088 { 089 DriverManager.registerDriver(new Driver()); 090 } 091 catch (SQLException e) 092 { 093 logger.log(Level.ERROR, Messages.DRIVER_REGISTER_FAILED.getMessage(Driver.class.getName()), e); 094 } 095 } 096 097 public static void stop(String id) throws SQLException 098 { 099 registry.remove(id); 100 } 101 102 public static void setFactory(DatabaseClusterFactory<java.sql.Driver, DriverDatabase> databaseClusterFactory) 103 { 104 factory = databaseClusterFactory; 105 } 106 107 public static void setConfigurationFactory(String id, DatabaseClusterConfigurationFactory<java.sql.Driver, DriverDatabase> configurationFactory) 108 { 109 configurationFactories.put(id, configurationFactory); 110 } 111 112 public static void setTimeout(long value, TimeUnit unit) 113 { 114 timeout = new TimePeriod(value, unit); 115 } 116 117 /** 118 * {@inheritDoc} 119 * @see net.sf.hajdbc.AbstractDriver#getUrlPattern() 120 */ 121 @Override 122 protected Pattern getUrlPattern() 123 { 124 return URL_PATTERN; 125 } 126 127 /** 128 * {@inheritDoc} 129 * @see java.sql.Driver#connect(java.lang.String, java.util.Properties) 130 */ 131 @Override 132 public Connection connect(String url, final Properties properties) throws SQLException 133 { 134 String id = this.parse(url); 135 136 // JDBC spec compliance 137 if (id == null) return null; 138 139 DatabaseCluster<java.sql.Driver, DriverDatabase> cluster = registry.get(id, properties); 140 DriverProxyFactory driverFactory = new DriverProxyFactory(cluster); 141 java.sql.Driver driver = driverFactory.createProxy(); 142 TransactionContext<java.sql.Driver, DriverDatabase> context = new LocalTransactionContext<java.sql.Driver, DriverDatabase>(cluster); 143 144 DriverInvoker<Connection> invoker = new DriverInvoker<Connection>() 145 { 146 @Override 147 public Connection invoke(DriverDatabase database, java.sql.Driver driver) throws SQLException 148 { 149 return driver.connect(database.getLocation(), properties); 150 } 151 }; 152 153 ConnectionProxyFactoryFactory<java.sql.Driver, DriverDatabase, java.sql.Driver> factory = new ConnectionProxyFactoryFactory<java.sql.Driver, DriverDatabase, java.sql.Driver>(context); 154 return factory.createProxyFactory(driver, driverFactory, invoker, InvocationStrategies.INVOKE_ON_ALL.invoke(driverFactory, invoker)).createProxy(); 155 } 156 157 /** 158 * {@inheritDoc} 159 * @see Driver#getPropertyInfo(java.lang.String, java.util.Properties) 160 */ 161 @Override 162 public DriverPropertyInfo[] getPropertyInfo(String url, final Properties properties) throws SQLException 163 { 164 String id = this.parse(url); 165 166 // JDBC spec compliance 167 if (id == null) return null; 168 169 DatabaseCluster<java.sql.Driver, DriverDatabase> cluster = registry.get(id, properties); 170 DriverProxyFactory map = new DriverProxyFactory(cluster); 171 172 DriverInvoker<DriverPropertyInfo[]> invoker = new DriverInvoker<DriverPropertyInfo[]>() 173 { 174 @Override 175 public DriverPropertyInfo[] invoke(DriverDatabase database, java.sql.Driver driver) throws SQLException 176 { 177 return driver.getPropertyInfo(database.getLocation(), properties); 178 } 179 }; 180 181 SortedMap<DriverDatabase, DriverPropertyInfo[]> results = InvocationStrategies.INVOKE_ON_ANY.invoke(map, invoker); 182 return results.get(results.firstKey()); 183 } 184 185 /** 186 * @see java.sql.Driver#getParentLogger() 187 */ 188 @Override 189 public java.util.logging.Logger getParentLogger() 190 { 191 return java.util.logging.Logger.getGlobal(); 192 } 193 194 private interface DriverInvoker<R> extends Invoker<java.sql.Driver, DriverDatabase, java.sql.Driver, R, SQLException> 195 { 196 } 197}