/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the  "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.xalan.lib.sql;

import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;

import javax.naming.InitialContext;
import javax.naming.NamingException;


/**
 * A Connection Pool that wraps a JDBC datasource to provide connections.
 * 
 * An instance of this class is created by <code>XConnection</code> when it
 * attempts to resolves a <code>ConnectionPool</code> name as a JNDI data source.
 * 
 * Most methods in this implementation do nothing since configuration is handled
 * by the underlying JDBC datasource.  Users should always call
 * <code>XConnection.close()</code> from their stylsheet to explicitely close
 * their connection.  However, since there is no way to enforce this
 * (Yikes!), it is recommended that a relatively short datasource timeout
 * be used to prevent dangling connections.
 */
public class JNDIConnectionPool implements ConnectionPool
{
  
  /**
   * Reference to the datasource
   */
  protected Object jdbcSource = null;

  /** 
   * To maintain Java 1.3 compatibility, we need to work with the
   * DataSource class through Reflection. The getConnection method
   * is one of the methods used, and there are two different flavors.
   * 
   */  
  private Method getConnectionWithArgs = null;
  private Method getConnection = null;
  
  
  /**
   * The unique jndi path for this datasource.
   */
  protected String jndiPath = null;
  
  /**
   * User name for protected datasources.
   */
  protected String user = null;
  
  /**
   * Password for protected datasources.
   */
  protected String pwd = null;
  
  /**
   * Use of the default constructor requires the jndi path to be set via
   * setJndiPath().
   */
  public JNDIConnectionPool() {  }
  
  /**
   * Creates a connection pool with a specified JNDI path. 
   * @param jndiDatasourcePath Complete path to the JNDI datasource
   */
  public JNDIConnectionPool(String jndiDatasourcePath)
  {
    jndiPath = jndiDatasourcePath.trim();
  }
  
  /**
   * Sets the path for the jndi datasource 
   * @param jndiPath 
   */
  public void setJndiPath(String jndiPath)
  {
    this.jndiPath = jndiPath;
  }

  /**
   * Returns the path for the jndi datasource 
   * @param jndiPath 
   */
  public String getJndiPath()
  {
    return jndiPath;
  }

  /**
   * Always returns true.
   * This method was intended to indicate if the pool was enabled, however, in
   * this implementation that is not relavant.
   * @return 
   */
  public boolean isEnabled()
  {
    return true;
  }

  /**
   * Not implemented and will throw an Error if called.
   * 
   * Connection configuration is handled by the underlying JNDI DataSource.
   * @param d 
   */
  public void setDriver(String d)
  {
    throw new Error(
      "This method is not supported. " +
      "All connection information is handled by the JDBC datasource provider");
  }

  /**
   * Not implemented and will throw an Error if called.
   * 
   * Connection configuration is handled by the underlying JNDI DataSource.
   * @param d 
   */
  public void setURL(String url)
  {
    throw new Error(
      "This method is not supported. " +
      "All connection information is handled by the JDBC datasource provider");
  }

  /**
   * Intended to release unused connections from the pool.
   * Does nothing in this implementation.
   */
  public void freeUnused()
  {
    //Do nothing - not an error to call this method
  }

  /**
   * Always returns false, indicating that this wrapper has no idea of what
   * connections the underlying JNDI source is maintaining.
   * @return 
   */
  public boolean hasActiveConnections()
  {
    return false;
  }

  /**
   * Sets the password for the connection.
   * If the jndi datasource does not require a password (which is typical),
   * this can be left null.
   * @param p the password
   */
  public void setPassword(String p)
  {
    
    if (p != null) p = p.trim();
    if (p != null && p.length() == 0) p = null;
    
    pwd = p;
  }

  /**
   * Sets the user name for the connection.
   * If the jndi datasource does not require a user name (which is typical),
   * this can be left null.
   * @param u the user name
   */
  public void setUser(String u)
  {
    
    if (u != null) u = u.trim();
    if (u != null && u.length() == 0) u = null;
    
    user = u;
  }

  /**
   * Returns a connection from the JDNI DataSource found at the JNDI Datasource
   * path.
   * 
   * @return 
   * @throws SQLException 
   */
  public Connection getConnection() throws SQLException
  {
    if (jdbcSource == null)
    {
      try
      {
        findDatasource();
      }
      catch (NamingException ne)
      {
        throw new SQLException(
          "Could not create jndi context for " + 
          jndiPath + " - " + ne.getLocalizedMessage());
      }
    }
    
    try
    {
      if (user != null || pwd != null)
      {
        Object arglist[] = { user, pwd }; 
        return (Connection) getConnectionWithArgs.invoke(jdbcSource, arglist);
      }
      else
      {
        Object arglist[] = {}; 
        return (Connection) getConnection.invoke(jdbcSource, arglist);
      }
    }
    catch (Exception e)
    {
      throw new SQLException(
        "Could not create jndi connection for " + 
        jndiPath + " - " + e.getLocalizedMessage());
    }
    
  }
  
  /**
   * Internal method used to look up the datasource. 
   * @throws NamingException 
   */
  protected void findDatasource() throws NamingException
  {
    try
    {
      InitialContext context = new InitialContext();
      jdbcSource =  context.lookup(jndiPath);
      
      Class withArgs[] = { String.class, String.class };
      getConnectionWithArgs = 
        jdbcSource.getClass().getDeclaredMethod("getConnection", withArgs);
      
      Class noArgs[] = { };
      getConnection = 
        jdbcSource.getClass().getDeclaredMethod("getConnection", noArgs);
      
    }
    catch (NamingException e)
    {
      throw e;
    }
    catch (NoSuchMethodException e)
    {
      // For simpleification, we will just throw a NamingException. We will only
      // use the message part of the exception anyway.
      throw new NamingException("Unable to resolve JNDI DataSource - " + e);
    }
  }

  public void releaseConnection(Connection con) throws SQLException
  {
    con.close();
  }

  public void releaseConnectionOnError(Connection con) throws SQLException
  {
    con.close();
  }

  /**
   * Releases the reference to the jndi datasource.
   * The original intention of this method was to actually turn the pool *off*.
   * Since we are not managing the pool, we simply release our reference to
   * the datasource.  Future calls to the getConnection will simply recreate
   * the datasource.
   * @param flag If false, the reference to the datasource is released.
   */
  public void setPoolEnabled(boolean flag)
  {
    if (! flag) jdbcSource = null;
  }

  /**
   * Ignored in this implementation b/c the pooling is determined by the jndi dataosource. 
   * @param p
   */
  public void setProtocol(Properties p)
  {
    /* ignore - properties are determined by datasource */
  }
  
  /**
   * Ignored in this implementation b/c the pooling is determined by the jndi dataosource. 
   * @param n 
   */
  public void setMinConnections(int n)
  {
    /* ignore - pooling is determined by datasource */
  }

  /**
   * A simple test to see if the jndi datasource exists.
   * 
   * Note that this test does not ensure that the datasource will return valid
   * connections.
   */
  public boolean testConnection()
  {
    if (jdbcSource == null)
    {
      try
      {
        findDatasource();
      }
      catch (NamingException ne)
      {
        return false;
      }
    }
    
    return true;
  }



}
