/*
  Copyright (c) 2002, 2014, Oracle and/or its affiliates. All rights reserved.

  The MySQL Connector/J is licensed under the terms of the GPLv2
  <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most MySQL Connectors.
  There are special exceptions to the terms and conditions of the GPLv2 as it is applied to
  this software, see the FLOSS License Exception
  <http://www.mysql.com/about/legal/licensing/foss-exception.html>.

  This program is free software; you can redistribute it and/or modify it under the terms
  of the GNU General Public License as published by the Free Software Foundation; version 2
  of the License.

  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 General Public License for more details.

  You should have received a copy of the GNU General Public License along with this
  program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth
  Floor, Boston, MA 02110-1301  USA

 */

package testsuite.regression;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Hashtable;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.Name;
import javax.naming.NameParser;
import javax.naming.RefAddr;
import javax.naming.Reference;
import javax.naming.spi.ObjectFactory;
import javax.sql.ConnectionPoolDataSource;
import javax.sql.DataSource;
import javax.sql.PooledConnection;

import testsuite.BaseTestCase;
import testsuite.simple.DataSourceTest;

import com.mysql.jdbc.ConnectionProperties;
import com.mysql.jdbc.NonRegisteringDriver;
import com.mysql.jdbc.integration.jboss.MysqlValidConnectionChecker;
import com.mysql.jdbc.jdbc2.optional.MysqlConnectionPoolDataSource;
import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;
import com.mysql.jdbc.jdbc2.optional.MysqlDataSourceFactory;
import com.mysql.jdbc.jdbc2.optional.MysqlXADataSource;

/**
 * Tests fixes for bugs related to datasources.
 * 
 * @author Mark Matthews
 * 
 * @version $Id: DataSourceRegressionTest.java,v 1.1.2.1 2005/05/13 18:58:38
 *          mmatthews Exp $
 */
public class DataSourceRegressionTest extends BaseTestCase {

	public final static String DS_DATABASE_PROP_NAME = "com.mysql.jdbc.test.ds.db";

	public final static String DS_HOST_PROP_NAME = "com.mysql.jdbc.test.ds.host";

	public final static String DS_PASSWORD_PROP_NAME = "com.mysql.jdbc.test.ds.password";

	public final static String DS_PORT_PROP_NAME = "com.mysql.jdbc.test.ds.port";

	public final static String DS_USER_PROP_NAME = "com.mysql.jdbc.test.ds.user";

	private Context ctx;

	private File tempDir;

	/**
	 * Creates a new DataSourceRegressionTest suite for the given test name
	 * 
	 * @param name
	 *            the name of the testcase to run.
	 */
	public DataSourceRegressionTest(String name) {
		super(name);
	}

	/**
	 * Runs all test cases in this test suite
	 * 
	 * @param args
	 */
	public static void main(String[] args) {
		junit.textui.TestRunner.run(DataSourceTest.class);
	}

	/**
	 * Sets up this test, calling registerDataSource() to bind a DataSource into
	 * JNDI, using the FSContext JNDI provider from Sun
	 * 
	 * @throws Exception
	 *             if an error occurs.
	 */
	public void setUp() throws Exception {
		super.setUp();
		createJNDIContext();
	}

	/**
	 * Un-binds the DataSource, and cleans up the filesystem
	 * 
	 * @throws Exception
	 *             if an error occurs
	 */
	public void tearDown() throws Exception {
		this.ctx.unbind(this.tempDir.getAbsolutePath() + "/test");
		this.ctx.unbind(this.tempDir.getAbsolutePath() + "/testNoUrl");
		this.ctx.close();
		this.tempDir.delete();

		super.tearDown();
	}

	/**
	 * Tests fix for BUG#4808- Calling .close() twice on a PooledConnection
	 * causes NPE.
	 * 
	 * @throws Exception
	 *             if an error occurs.
	 */
	public void testBug4808() throws Exception {
		MysqlConnectionPoolDataSource ds = new MysqlConnectionPoolDataSource();
		ds.setURL(BaseTestCase.dbUrl);
		PooledConnection closeMeTwice = ds.getPooledConnection();
		closeMeTwice.close();
		closeMeTwice.close();

	}

	/**
	 * Tests fix for Bug#3848, port # alone parsed incorrectly
	 * 
	 * @throws Exception
	 *             ...
	 */
	public void testBug3848() throws Exception {
		String jndiName = "/testBug3848";

		String databaseName = System.getProperty(DS_DATABASE_PROP_NAME);
		String userName = System.getProperty(DS_USER_PROP_NAME);
		String password = System.getProperty(DS_PASSWORD_PROP_NAME);
		String port = System.getProperty(DS_PORT_PROP_NAME);

		// Only run this test if at least one of the above are set
		if ((databaseName != null) || (userName != null) || (password != null)
				|| (port != null)) {
			MysqlConnectionPoolDataSource ds = new MysqlConnectionPoolDataSource();

			if (databaseName != null) {
				ds.setDatabaseName(databaseName);
			}

			if (userName != null) {
				ds.setUser(userName);
			}

			if (password != null) {
				ds.setPassword(password);
			}

			if (port != null) {
				ds.setPortNumber(Integer.parseInt(port));
			}

			bindDataSource(jndiName, ds);

			ConnectionPoolDataSource boundDs = null;

			try {
				boundDs = (ConnectionPoolDataSource) lookupDatasourceInJNDI(jndiName);

				assertTrue("Datasource not bound", boundDs != null);

				Connection dsConn = null;

				try {
					dsConn = boundDs.getPooledConnection().getConnection();
				} finally {
					if (dsConn != null) {
						dsConn.close();
					}
				}
			} finally {
				if (boundDs != null) {
					this.ctx.unbind(jndiName);
				}
			}
		}
	}

	/**
	 * Tests that we can get a connection from the DataSource bound in JNDI
	 * during test setup
	 * 
	 * @throws Exception
	 *             if an error occurs
	 */
	public void testBug3920() throws Exception {
		String jndiName = "/testBug3920";

		String databaseName = System.getProperty(DS_DATABASE_PROP_NAME);
		String userName = System.getProperty(DS_USER_PROP_NAME);
		String password = System.getProperty(DS_PASSWORD_PROP_NAME);
		String port = System.getProperty(DS_PORT_PROP_NAME);
		String serverName = System.getProperty(DS_HOST_PROP_NAME);

		// Only run this test if at least one of the above are set
		if ((databaseName != null) || (serverName != null)
				|| (userName != null) || (password != null) || (port != null)) {
			MysqlConnectionPoolDataSource ds = new MysqlConnectionPoolDataSource();

			if (databaseName != null) {
				ds.setDatabaseName(databaseName);
			}

			if (userName != null) {
				ds.setUser(userName);
			}

			if (password != null) {
				ds.setPassword(password);
			}

			if (port != null) {
				ds.setPortNumber(Integer.parseInt(port));
			}

			if (serverName != null) {
				ds.setServerName(serverName);
			}

			bindDataSource(jndiName, ds);

			ConnectionPoolDataSource boundDs = null;

			try {
				boundDs = (ConnectionPoolDataSource) lookupDatasourceInJNDI(jndiName);

				assertTrue("Datasource not bound", boundDs != null);

				Connection dsCon = null;
				Statement dsStmt = null;

				try {
					dsCon = boundDs.getPooledConnection().getConnection();
					dsStmt = dsCon.createStatement();
					dsStmt.executeUpdate("DROP TABLE IF EXISTS testBug3920");
					dsStmt
							.executeUpdate("CREATE TABLE testBug3920 (field1 varchar(32))");

					assertTrue(
							"Connection can not be obtained from data source",
							dsCon != null);
				} finally {
					dsStmt.executeUpdate("DROP TABLE IF EXISTS testBug3920");

					dsStmt.close();
					dsCon.close();
				}
			} finally {
				if (boundDs != null) {
					this.ctx.unbind(jndiName);
				}
			}
		}
	}

	/** 
	 * Tests fix for BUG#19169 - ConnectionProperties (and thus some
	 * subclasses) are not serializable, even though some J2EE containers
	 * expect them to be.
	 * 
	 * @throws Exception if the test fails.
	 */
	public void testBug19169() throws Exception {
		MysqlDataSource toSerialize = new MysqlDataSource();
		toSerialize.setZeroDateTimeBehavior("convertToNull");
		
		boolean testBooleanFlag = !toSerialize.getAllowLoadLocalInfile();
		toSerialize.setAllowLoadLocalInfile(testBooleanFlag);
		
		int testIntFlag = toSerialize.getBlobSendChunkSize() + 1;
		toSerialize.setBlobSendChunkSize(String.valueOf(testIntFlag));
		
		ByteArrayOutputStream bOut = new ByteArrayOutputStream();
		ObjectOutputStream objOut = new ObjectOutputStream(bOut);
		objOut.writeObject(toSerialize);
		objOut.flush();
		
		ObjectInputStream objIn = new ObjectInputStream(new ByteArrayInputStream(bOut.toByteArray()));
		
		MysqlDataSource thawedDs = (MysqlDataSource)objIn.readObject();
		
		assertEquals("convertToNull", thawedDs.getZeroDateTimeBehavior());
		assertEquals(testBooleanFlag, thawedDs.getAllowLoadLocalInfile());
		assertEquals(testIntFlag, thawedDs.getBlobSendChunkSize());
	}
	
	/**
	 * Tests fix for BUG#20242 - MysqlValidConnectionChecker for JBoss doesn't
	 * work with MySQLXADataSources.
	 * 
	 * @throws Exception if the test fails.
	 */
	public void testBug20242() throws Exception {
		if (versionMeetsMinimum(5, 0)) {
			try {
				Class.forName("org.jboss.resource.adapter.jdbc.ValidConnectionChecker");
			} catch (Exception ex) {
				return; // class not available for testing
			}
			
			MysqlXADataSource xaDs = new MysqlXADataSource();
			xaDs.setUrl(dbUrl);
			
			MysqlValidConnectionChecker checker = new MysqlValidConnectionChecker();
			assertNull(checker.isValidConnection(xaDs.getXAConnection().getConnection()));
		}	
	}
	
	private void bindDataSource(String name, DataSource ds) throws Exception {
		this.ctx.bind(this.tempDir.getAbsolutePath() + name, ds);
	}

	/**
	 * This method is separated from the rest of the example since you normally
	 * would NOT register a JDBC driver in your code. It would likely be
	 * configered into your naming and directory service using some GUI.
	 * 
	 * @throws Exception
	 *             if an error occurs
	 */
	private void createJNDIContext() throws Exception {
		this.tempDir = File.createTempFile("jnditest", null);
		this.tempDir.delete();
		this.tempDir.mkdir();
		this.tempDir.deleteOnExit();

		MysqlConnectionPoolDataSource ds;
		Hashtable<String, String> env = new Hashtable<String, String>();
		env.put(Context.INITIAL_CONTEXT_FACTORY,
				"com.sun.jndi.fscontext.RefFSContextFactory");
		this.ctx = new InitialContext(env);
		assertTrue("Naming Context not created", this.ctx != null);
		ds = new MysqlConnectionPoolDataSource();
		ds.setUrl(dbUrl); // from BaseTestCase
		ds.setDatabaseName("test");
		this.ctx.bind(this.tempDir.getAbsolutePath() + "/test", ds);
	}

	private DataSource lookupDatasourceInJNDI(String jndiName) throws Exception {
		NameParser nameParser = this.ctx.getNameParser("");
		Name datasourceName = nameParser.parse(this.tempDir.getAbsolutePath()
				+ jndiName);
		Object obj = this.ctx.lookup(datasourceName);
		DataSource boundDs = null;

		if (obj instanceof DataSource) {
			boundDs = (DataSource) obj;
		} else if (obj instanceof Reference) {
			//
			// For some reason, this comes back as a Reference
			// instance under CruiseControl !?
			//
			Reference objAsRef = (Reference) obj;
			ObjectFactory factory = (ObjectFactory) Class.forName(
					objAsRef.getFactoryClassName()).newInstance();
			boundDs = (DataSource) factory.getObjectInstance(objAsRef,
					datasourceName, this.ctx, new Hashtable<Object, Object>());
		}

		return boundDs;
	}

	public void testCSC4616() throws Exception {
		MysqlConnectionPoolDataSource ds = new MysqlConnectionPoolDataSource();
		ds.setURL(BaseTestCase.dbUrl);
		PooledConnection pooledConn = ds.getPooledConnection();
		Connection physConn = pooledConn.getConnection();
		Statement physStatement = physConn.createStatement();

		Method enableStreamingResultsMethodStmt = Class.forName(
				"com.mysql.jdbc.jdbc2.optional.StatementWrapper").getMethod(
				"enableStreamingResults", new Class[0]);
		enableStreamingResultsMethodStmt.invoke(physStatement, (Object[])null);
		this.rs = physStatement.executeQuery("SELECT 1");

		try {
			physConn.createStatement().executeQuery("SELECT 2");
			fail("Should have caught a streaming exception here");
		} catch (SQLException sqlEx) {
			assertTrue(sqlEx.getMessage() != null
					&& sqlEx.getMessage().indexOf("Streaming") != -1);
		} finally {
			if (this.rs != null) {
				this.rs.close();
				this.rs = null;
			}
		}

		PreparedStatement physPrepStmt = physConn.prepareStatement("SELECT 1");
		Method enableStreamingResultsMethodPstmt = Class.forName(
				"com.mysql.jdbc.jdbc2.optional.PreparedStatementWrapper")
				.getMethod("enableStreamingResults", (Class[])null);
		enableStreamingResultsMethodPstmt.invoke(physPrepStmt, (Object[])null);

		this.rs = physPrepStmt.executeQuery();

		try {
			physConn.createStatement().executeQuery("SELECT 2");
			fail("Should have caught a streaming exception here");
		} catch (SQLException sqlEx) {
			assertTrue(sqlEx.getMessage() != null
					&& sqlEx.getMessage().indexOf("Streaming") != -1);
		} finally {
			if (this.rs != null) {
				this.rs.close();
				this.rs = null;
			}
		}
	}

	/**
	 * Tests fix for BUG#16791 - NullPointerException in MysqlDataSourceFactory
	 * due to Reference containing RefAddrs with null content.
	 * 
	 * @throws Exception if the test fails
	 */
	public void testBug16791() throws Exception {
		MysqlDataSource myDs = new MysqlDataSource();
		myDs.setUrl(dbUrl);
		Reference asRef = myDs.getReference();
		System.out.println(asRef);
		
		removeFromRef(asRef, "port");
		removeFromRef(asRef, NonRegisteringDriver.USER_PROPERTY_KEY);
		removeFromRef(asRef, NonRegisteringDriver.PASSWORD_PROPERTY_KEY);
		removeFromRef(asRef, "serverName");
		removeFromRef(asRef, "databaseName");
		
		//MysqlDataSource newDs = (MysqlDataSource)
		new MysqlDataSourceFactory().getObjectInstance(asRef, null, null, null);
	}

	private void removeFromRef(Reference ref, String key) {
		int size = ref.size();
		
		for (int i = 0; i < size; i++) {
			RefAddr refAddr = ref.get(i);
			if (refAddr.getType().equals(key)) {
				ref.remove(i);
				break;
			}
		}
	}
	
	/**
	 * Tests fix for BUG#32101 - When using a connection from our ConnectionPoolDataSource,
     * some Connection.prepareStatement() methods would return null instead of
     * a prepared statement.
     * 
	 * @throws Exception
	 */
	public void testBug32101() throws Exception {
		MysqlConnectionPoolDataSource ds = new MysqlConnectionPoolDataSource();
		ds.setURL(BaseTestCase.dbUrl);
		PooledConnection pc = ds.getPooledConnection();
		assertNotNull(pc.getConnection().prepareStatement("SELECT 1"));
		assertNotNull(pc.getConnection().prepareStatement("SELECT 1", Statement.RETURN_GENERATED_KEYS));
		assertNotNull(pc.getConnection().prepareStatement("SELECT 1", new int[0]));
		assertNotNull(pc.getConnection().prepareStatement("SELECT 1", new String[0]));
		assertNotNull(pc.getConnection().prepareStatement("SELECT 1", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY));
		assertNotNull(pc.getConnection().prepareStatement("SELECT 1", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.HOLD_CURSORS_OVER_COMMIT));
	}

	public void testBug35810() throws Exception {
		int defaultConnectTimeout = ((ConnectionProperties) this.conn).getConnectTimeout();
		int nonDefaultConnectTimeout = defaultConnectTimeout + 1000 * 2;
		MysqlConnectionPoolDataSource cpds = new MysqlConnectionPoolDataSource();
		String dsUrl = BaseTestCase.dbUrl;
		if (dsUrl.indexOf("?") == -1) {
			dsUrl += "?";
		} else {
			dsUrl += "&";
		}
		
		dsUrl += "connectTimeout=" + nonDefaultConnectTimeout;
		cpds.setUrl(dsUrl);
		
		Connection dsConn = cpds.getPooledConnection().getConnection();
		int configuredConnectTimeout = ((ConnectionProperties) dsConn).getConnectTimeout();
		
		assertEquals("Connect timeout spec'd by URL didn't take", nonDefaultConnectTimeout, configuredConnectTimeout);
		assertFalse("Connect timeout spec'd by URL didn't take", defaultConnectTimeout == configuredConnectTimeout);
	}

	public void testBug42267() throws Exception {
		MysqlDataSource ds = new MysqlDataSource();
		ds.setUrl(dbUrl);
		Connection conn = ds.getConnection();
		String query = "select 1,2,345";
		PreparedStatement ps = conn.prepareStatement(query);
		String psString = ps.toString();
		assertTrue("String representation of wrapped ps should contain query string", psString.endsWith(": " + query));
		ps.close();
		ps.toString();
		conn.close();
	}
}
