package com.mir3.sawtooth.service.piimpl;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.DriverPropertyInfo;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Enumeration;
import java.util.Properties;

import org.apache.commons.collections.LRUMap;

/**
 * Wraps a real JDBC driver to implement prepared statement pooling.
 * @author Mark Lewis
 * Mar 19, 2007
 */
public class PoolingDriver implements Driver {

	public PoolingDriver(Driver realDriver) {
		this.realDriver = realDriver;
	}
	
	public Connection connect(String url, Properties info) throws SQLException {
		return wrap(realDriver.connect(url, info));
	}

	public boolean acceptsURL(String url) throws SQLException {
		return realDriver.acceptsURL(url);
	}

	public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws SQLException {
		return realDriver.getPropertyInfo(url, info);
	}

	public int getMajorVersion() {
		return realDriver.getMajorVersion();
	}

	public int getMinorVersion() {
		return realDriver.getMinorVersion();
	}

	public boolean jdbcCompliant() {
		return realDriver.jdbcCompliant();
	}
	
	private Connection wrap(final Connection con) {
		ClassLoader loader = getClass().getClassLoader();
		return (Connection)Proxy.newProxyInstance(loader, new Class[]{Connection.class}, new InvocationHandler() {
			public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
				try {
					if(method.getReturnType().isInstance(PreparedStatement.class)) {
						String sql = (String)args[0];
						PreparedStatement ps = (PreparedStatement) statementCache.get(sql);
						if(ps == null) {
							ps = (PreparedStatement) method.invoke(con, args);
							statementCache.put(sql, ps);
						}
						return ps;
					}
					else {
						return method.invoke(con, args);
					}
				}
				catch(InvocationTargetException ex) {
					throw ex.getCause();
				}
			}
			
			private LRUMap statementCache = new LRUMap(100);
		});
	}
	
	private Driver realDriver;
	
	static {
		try {
			// Load the real class, then deregister its driver and register mine in its place.
			Class realClass = Class.forName("org.postgresql.Driver");
			Driver realDriver = null;
			for(Enumeration e=DriverManager.getDrivers(); e.hasMoreElements(); ) {
				Driver d = (Driver) e.nextElement();
				if(realClass.isInstance(d)) {
					realDriver = d;
					DriverManager.deregisterDriver(d);
					break;
				}
			}
			PoolingDriver driver = new PoolingDriver(realDriver);
			DriverManager.registerDriver(driver);
		}
		catch (Exception ex) {
			ex.printStackTrace();
		}
	}
}
