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}