/*
 Copyright (c) 2002, 2012, 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 com.mysql.jdbc.jdbc2.optional;

import java.io.PrintWriter;
import java.io.Serializable;
import java.sql.SQLException;
import java.util.Iterator;
import java.util.Properties;

import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.Referenceable;
import javax.naming.StringRefAddr;
import javax.sql.DataSource;

import com.mysql.jdbc.ConnectionPropertiesImpl;
import com.mysql.jdbc.NonRegisteringDriver;

/**
 * A JNDI DataSource for a Mysql JDBC connection
 * 
 * @author Mark Matthews
 */
public class MysqlDataSource extends ConnectionPropertiesImpl implements
		DataSource, Referenceable, Serializable  {

	static final long serialVersionUID = -5515846944416881264L;

	/** The driver to create connections with */
	protected final static NonRegisteringDriver mysqlDriver;

	static {
		try {
			mysqlDriver = new NonRegisteringDriver();
		} catch (Exception E) {
			throw new RuntimeException(
					"Can not load Driver class com.mysql.jdbc.Driver");
		}
	}

	/** Log stream */
	protected transient PrintWriter logWriter = null;

	/** Database Name */
	protected String databaseName = null;

	/** Character Encoding */
	protected String encoding = null;

	/** Hostname */
	protected String hostName = null;

	/** Password */
	protected String password = null;

	/** The profileSql property */
	protected String profileSql = "false";

	/** The JDBC URL */
	protected String url = null;

	/** User name */
	protected String user = null;

	/** Should we construct the URL, or has it been set explicitly */
	protected boolean explicitUrl = false;

	/** Port number */
	protected int port = 3306;

	/**
	 * Default no-arg constructor for Serialization
	 */
	public MysqlDataSource() {
	}

	/**
	 * Creates a new connection using the already configured username and
	 * password.
	 * 
	 * @return a connection to the database
	 * 
	 * @throws SQLException
	 *             if an error occurs
	 */
	public java.sql.Connection getConnection() throws SQLException {
		return getConnection(this.user, this.password);
	}

	/**
	 * Creates a new connection with the given username and password
	 * 
	 * @param userID
	 *            the user id to connect with
	 * @param password
	 *            the password to connect with
	 * 
	 * @return a connection to the database
	 * 
	 * @throws SQLException
	 *             if an error occurs
	 */
	public java.sql.Connection getConnection(String userID, String pass)
			throws SQLException {
		Properties props = new Properties();

		if (userID != null) {
			props.setProperty(NonRegisteringDriver.USER_PROPERTY_KEY, userID);
		}

		if (pass != null) {
			props.setProperty(NonRegisteringDriver.PASSWORD_PROPERTY_KEY, pass);
		}

		exposeAsProperties(props);

		return getConnection(props);
	}

	/**
	 * Sets the database name.
	 * 
	 * @param dbName
	 *            the name of the database
	 */
	public void setDatabaseName(String dbName) {
		this.databaseName = dbName;
	}

	/**
	 * Gets the name of the database
	 * 
	 * @return the name of the database for this data source
	 */
	public String getDatabaseName() {
		return (this.databaseName != null) ? this.databaseName : "";
	}

	/**
	 * Sets the log writer for this data source.
	 * 
	 * @see javax.sql.DataSource#setLogWriter(PrintWriter)
	 */
	public void setLogWriter(PrintWriter output) throws SQLException {
		this.logWriter = output;
	}

	/**
	 * Returns the log writer for this data source
	 * 
	 * @return the log writer for this data source
	 */
	public java.io.PrintWriter getLogWriter() {
		return this.logWriter;
	}

	/**
	 * DOCUMENT ME!
	 * 
	 * @param seconds
	 *            DOCUMENT ME!
	 * 
	 * @throws SQLException
	 *             DOCUMENT ME!
	 */
	public void setLoginTimeout(int seconds) throws SQLException {
	}

	/**
	 * Returns the login timeout
	 * 
	 * @return the login timeout
	 */
	public int getLoginTimeout() {
		return 0;
	}

	/**
	 * Sets the password
	 * 
	 * @param pass
	 *            the password
	 */
	public void setPassword(String pass) {
		this.password = pass;
	}

	/**
	 * Sets the database port.
	 * 
	 * @param p
	 *            the port
	 */
	public void setPort(int p) {
		this.port = p;
	}

	/**
	 * Returns the port number
	 * 
	 * @return the port number
	 */
	public int getPort() {
		return this.port;
	}

	/**
	 * Sets the port number
	 * 
	 * @param p
	 *            the port
	 * 
	 * @see #setPort
	 */
	public void setPortNumber(int p) {
		setPort(p);
	}

	/**
	 * Returns the port number
	 * 
	 * @return the port number
	 */
	public int getPortNumber() {
		return getPort();
	}

	/**
	 * DOCUMENT ME!
	 * 
	 * @param ref
	 *            DOCUMENT ME!
	 * 
	 * @throws SQLException
	 *             DOCUMENT ME!
	 */
	public void setPropertiesViaRef(Reference ref) throws SQLException {
		super.initializeFromRef(ref);
	}

	/**
	 * Required method to support this class as a <CODE>Referenceable</CODE>.
	 * 
	 * @return a Reference to this data source
	 * 
	 * @throws NamingException
	 *             if a JNDI error occurs
	 */
	public Reference getReference() throws NamingException {
		String factoryName = "com.mysql.jdbc.jdbc2.optional.MysqlDataSourceFactory";
		Reference ref = new Reference(getClass().getName(), factoryName, null);
		ref.add(new StringRefAddr(NonRegisteringDriver.USER_PROPERTY_KEY,
				getUser()));
		ref.add(new StringRefAddr(NonRegisteringDriver.PASSWORD_PROPERTY_KEY,
				this.password));
		ref.add(new StringRefAddr("serverName", getServerName()));
		ref.add(new StringRefAddr("port", "" + getPort()));
		ref.add(new StringRefAddr("databaseName", getDatabaseName()));
		ref.add(new StringRefAddr("url", getUrl()));
		ref.add(new StringRefAddr("explicitUrl", String
				.valueOf(this.explicitUrl)));

		//
		// Now store all of the 'non-standard' properties...
		//
		try {
			storeToRef(ref);
		} catch (SQLException sqlEx) {
			throw new NamingException(sqlEx.getMessage());
		}

		return ref;
	}

	/**
	 * Sets the server name.
	 * 
	 * @param serverName
	 *            the server name
	 */
	public void setServerName(String serverName) {
		this.hostName = serverName;
	}

	/**
	 * Returns the name of the database server
	 * 
	 * @return the name of the database server
	 */
	public String getServerName() {
		return (this.hostName != null) ? this.hostName : "";
	}

	//
	// I've seen application servers use both formats
	// URL or url (doh)
	//

	/**
	 * Sets the URL for this connection
	 * 
	 * @param url
	 *            the URL for this connection
	 */
	public void setURL(String url) {
		setUrl(url);
	}

	/**
	 * Returns the URL for this connection
	 * 
	 * @return the URL for this connection
	 */
	public String getURL() {
		return getUrl();
	}

	/**
	 * This method is used by the app server to set the url string specified
	 * within the datasource deployment descriptor. It is discovered using
	 * introspection and matches if property name in descriptor is "url".
	 * 
	 * @param url
	 *            url to be used within driver.connect
	 */
	public void setUrl(String url) {
		this.url = url;
		this.explicitUrl = true;
	}

	/**
	 * Returns the JDBC URL that will be used to create the database connection.
	 * 
	 * @return the URL for this connection
	 */
	public String getUrl() {
		if (!this.explicitUrl) {
			String builtUrl = "jdbc:mysql://";
			builtUrl = builtUrl + getServerName() + ":" + getPort() + "/"
					+ getDatabaseName();

			return builtUrl;
		}

		return this.url;
	}

	/**
	 * Sets the user ID.
	 * 
	 * @param userID
	 *            the User ID
	 */
	public void setUser(String userID) {
		this.user = userID;
	}

	/**
	 * Returns the configured user for this connection
	 * 
	 * @return the user for this connection
	 */
	public String getUser() {
		return this.user;
	}

	/**
	 * Creates a connection using the specified properties.
	 * 
	 * @param props
	 *            the properties to connect with
	 * 
	 * @return a connection to the database
	 * 
	 * @throws SQLException
	 *             if an error occurs
	 */
	protected java.sql.Connection getConnection(Properties props)
			throws SQLException {
		String jdbcUrlToUse = null;

		if (!this.explicitUrl) {
			StringBuffer jdbcUrl = new StringBuffer("jdbc:mysql://");

			if (this.hostName != null) {
				jdbcUrl.append(this.hostName);
			}

			jdbcUrl.append(":");
			jdbcUrl.append(this.port);
			jdbcUrl.append("/");

			if (this.databaseName != null) {
				jdbcUrl.append(this.databaseName);
			}

			jdbcUrlToUse = jdbcUrl.toString();
		} else {
			jdbcUrlToUse = this.url;
		}

		//
		// URL should take precedence over properties
		//
		
		Properties urlProps = mysqlDriver.parseURL(jdbcUrlToUse, null);
		urlProps.remove(NonRegisteringDriver.DBNAME_PROPERTY_KEY);
		urlProps.remove(NonRegisteringDriver.HOST_PROPERTY_KEY);
		urlProps.remove(NonRegisteringDriver.PORT_PROPERTY_KEY);
		
		Iterator<Object> keys = urlProps.keySet().iterator();
		
		while (keys.hasNext()) {
			String key = (String)keys.next();
			
			props.setProperty(key, urlProps.getProperty(key));
		}
		
		return mysqlDriver.connect(jdbcUrlToUse, props);
	}
//
//	public boolean isWrapperFor(Class<?> iface) throws SQLException {
//		throw SQLError.notImplemented();
//	}
//
//	public <T> T unwrap(Class<T> iface) throws SQLException {
//		throw SQLError.notImplemented();
//	}
}
