/*
 * 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.
 */
/*
 * $Id$
 */
package org.apache.xalan.lib.sql;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.Vector;

import javax.xml.transform.ErrorListener;
import javax.xml.transform.TransformerException;

import org.apache.xalan.extensions.ExpressionContext;
import org.apache.xml.dtm.DTM;
import org.apache.xml.dtm.DTMIterator;
import org.apache.xml.dtm.DTMManager;
import org.apache.xml.dtm.ref.DTMManagerDefault;
import org.apache.xml.dtm.ref.DTMNodeIterator;
import org.apache.xml.dtm.ref.DTMNodeProxy;
import org.apache.xpath.XPathContext;
import org.apache.xpath.objects.XBooleanStatic;
import org.apache.xpath.objects.XNodeSet;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * An XSLT extension that allows a stylesheet to
 * access JDBC data.
 *
 * It is accessed by specifying a namespace URI as follows:
 * <pre>
 *    xmlns:sql="http://xml.apache.org/xalan/sql"
 * </pre>
 *
 * From the stylesheet perspective,
 * XConnection provides 3 extension functions: new(),
 * query(), and close().
 * Use new() to call one of XConnection constructors, which
 * establishes a JDBC driver connection to a data source and
 * returns an XConnection object.
 * Then use the XConnection object query() method to return a
 * result set in the form of a row-set element.
 * When you have finished working with the row-set, call the
 * XConnection object close() method to terminate the connection.
 */
public class XConnection
{

  /**
   * Flag for DEBUG mode
   */
  private static final boolean DEBUG = false;

  /**
   * The Current Connection Pool in Use. An XConnection can only
   * represent one query at a time, prior to doing some type of query.
   */
  private ConnectionPool m_ConnectionPool = null;

  /**
   * The DBMS Connection used to produce this SQL Document.
   * Will be used to clear free up the database resources on
   * close.
   */
  private Connection m_Connection = null;

  /**
   * If a default Connection Pool is used. i.e. A connection Pool
   * that is created internally, then do we actually allow pools
   * to be created. Due to the archititure of the Xalan Extensions,
   * there is no notification of when the Extension is being unloaded and
   * as such, there is a good chance that JDBC COnnections are not closed.
   * A finalized is provided to try and catch this situation but since
   * support of finalizers is inconsistant across JVM's this may cause
   * a problem. The robustness of the JDBC Driver is also at issue here.
   * if a controlled shutdown is provided by the driver then default
   * conntectiom pools are OK.
   */
  private boolean m_DefaultPoolingEnabled = false;


  /**
   * As we do queries, we will produce SQL Documents. Any ony may produce
   * one or more SQL Documents so that the current connection information
   * may be easilly reused. This collection will hold a collection of all
   * the documents created. As Documents are closed, they will be removed
   * from the collection and told to free all the used resources.
   */
  private Vector m_OpenSQLDocuments = new Vector();


  /**
   * Let's keep a copy of the ConnectionPoolMgr in
   * alive here so we are keeping the static pool alive
   * We will also use this Pool Manager to register our default pools.
   */
  private ConnectionPoolManager m_PoolMgr = new ConnectionPoolManager();

  /**
   * For PreparedStatements, we need a place to
   * to store the parameters in a vector.
   */
  private Vector m_ParameterList = new Vector();

  /**
   * Allow the SQL Extensions to return null on error. The Error information will
   * be stored in a seperate Error Document that can easily be retrived using the
   * getError() method.
   * %REVIEW% This functionality will probably be buried inside the SQLDocument.
   */
  private Exception m_Error = null;

  /**
   * When the Stylesheet wants to review the errors from a paticular
   * document, it asks the XConnection. We need to track what document
   * in the list of managed documents caused the last error. As SetError
   * is called, it will record the document that had the error.
   */
  private SQLDocument     m_LastSQLDocumentWithError = null;

  /**
   * If true then full information should be returned about errors and warnings
   * in getError(). This includes chained errors and warnings.
   * If false (the default) then getError() returns just the first SQLException.
   */
  private boolean m_FullErrors = false;



  /**
   * One a per XConnection basis there is a master QueryParser that is responsible
   * for generating Query Parsers. This will allow us to cache previous instances
   * so the inline parser execution time is minimized.
   */
  private SQLQueryParser m_QueryParser = new SQLQueryParser();

  /**
   */
  private boolean m_IsDefaultPool = false;

  /**
   * This flag will be used to indicate to the SQLDocument to use
   * Streaming mode. Streeaming Mode will reduce the memory footprint
   * to a fixed amount but will not let you traverse the tree more than
   * once since the Row data will be reused for every Row in the Query.
   */
  private boolean m_IsStreamingEnabled = true;

  /**
   *
   */
   private boolean m_InlineVariables = false;

  /**
   * This flag will be used to indicate if multiple result sets are
   * supported from the database. If they are, then the metadata element is
   * moved to insude the row-set element and multiple row-set elements may
   * be included under the sql root element.
   */
  private boolean m_IsMultipleResultsEnabled = false;

  /**
   * This flag will be used to indicate if database preparedstatements
   * should be cached. This also controls if the Java statement object
   * should be cached.
   */
  private boolean m_IsStatementCachingEnabled = false;

  /**
   */
  public XConnection( )
  {
  }

  /**
   * Constructs a new XConnection and attempts to connect to a datasource as
   * defined in the
   * <code>connect(ExpressionContext exprContext, String connPoolName)</code>
   * method.
   * <code>org.apache.xalan.lib.sql.ConnectionPool</code> or a JNDI datasource.
   *
   * @param exprContext Context automatically passed from the XSLT sheet.
   * @param name The name of the ConnectionPool or the JNDI DataSource path.
   *
   */
  public XConnection( ExpressionContext exprContext, String connPoolName )
  {
    connect(exprContext, connPoolName);
  }

  /**
   * @param exprContext
   * @param driver
   * @param dbURL
   */
  public XConnection( ExpressionContext exprContext, String driver, String dbURL )
  {
    connect(exprContext, driver, dbURL);
  }

  /**
   * @param exprContext
   * @param list
   */
  public XConnection( ExpressionContext exprContext, NodeList list )
  {
    connect(exprContext, list);
  }

  /**
   * @param exprContext
   * @param driver
   * @param dbURL
   * @param user
   * @param password
   */
  public XConnection( ExpressionContext exprContext, String driver, String dbURL, String user, String password )
  {
    connect(exprContext, driver, dbURL, user, password);
  }

  /**
   * @param exprContext
   * @param driver
   * @param dbURL
   * @param protocolElem
   */
  public XConnection( ExpressionContext exprContext, String driver, String dbURL, Element protocolElem )
  {
    connect(exprContext, driver, dbURL, protocolElem);
  }

  /**
    * Returns an XConnection from either a user created
    * <code>org.apache.xalan.lib.sql.ConnectionPool</code> or a JNDI datasource.

    * 
    * This method first tries to resolve the passed name against
    * <code>ConnectionPool</code>s registered with
    * <code>ConnectionPoolManager</code>.
    * If that fails, it attempts to find the name as a JNDI DataSource path.
    *
    * @param exprContext Context automatically passed from the XSLT sheet.
    * @param name The name of the ConnectionPool or the JNDI DataSource path.
    *
    */
   public XBooleanStatic connect( ExpressionContext exprContext, String name )
   {
     try
     {
       m_ConnectionPool = m_PoolMgr.getPool(name);

       if (m_ConnectionPool == null)
       {
         //Try to create a jndi source with the passed name
         ConnectionPool pool = new JNDIConnectionPool(name);
        
         if (pool.testConnection())
         {
          
           //JNDIConnectionPool seems good, so register it with the pool manager.
           //Once registered, it will show up like other named ConnectionPool's,
           //so the m_PoolMgr.getPool(name) method (above) will find it.
           m_PoolMgr.registerPool(name, pool);
           m_ConnectionPool = pool;
          
           m_IsDefaultPool = false;
           return new XBooleanStatic(true);
         }
         else
         {
           throw new IllegalArgumentException(
               "Invalid ConnectionPool name or JNDI Datasource path: " + name);
         }
       }
       else
       {
         m_IsDefaultPool = false;
         return new XBooleanStatic(true);
       }
     }
     catch (Exception e)
     {
       setError(e, exprContext);
       return new XBooleanStatic(false);
     }

   }


  /**
   * Create an XConnection object with just a driver and database URL.
   * @param exprContext
   * @param driver JDBC driver of the form foo.bar.Driver.
   * @param dbURL database URL of the form jdbc:subprotocol:subname.
   *
   */
  public XBooleanStatic connect( ExpressionContext exprContext, String driver, String dbURL )
  {
    try
    {
      init(driver, dbURL, new Properties());
      return new XBooleanStatic(true);
    }
    catch(SQLException e)
    {
      setError(e,exprContext);
      return new XBooleanStatic(false);
    }
    catch (Exception e)
    {
      setError(e,exprContext);
      return new XBooleanStatic(false);
    }
  }

  /**
   * @param exprContext
   * @param protocolElem
   *
   */
  public XBooleanStatic connect( ExpressionContext exprContext, Element protocolElem )
  {
    try
    {
      initFromElement(protocolElem);
      return new XBooleanStatic(true);
    }
    catch(SQLException e)
    {
      setError(e,exprContext);
      return new XBooleanStatic(false);
    }
    catch (Exception e)
    {
      setError(e,exprContext);
      return new XBooleanStatic(false);
    }
  }

  /**
   * @param exprContext
   * @param list
   *
   */
  public XBooleanStatic connect( ExpressionContext exprContext, NodeList list )
  {
    try
    {
      initFromElement( (Element) list.item(0) );
      return new XBooleanStatic(true);
    }
    catch(SQLException e)
    {
      setError(e, exprContext);
      return new XBooleanStatic(false);
    }
    catch (Exception e)
    {
      setError(e,exprContext);
      return new XBooleanStatic(false);
    }
  }

  /**
   * Create an XConnection object with user ID and password.
   * @param exprContext
   * @param driver JDBC driver of the form foo.bar.Driver.
   * @param dbURL database URL of the form jdbc:subprotocol:subname.
   * @param user user ID.
   * @param password connection password.
   *
   */
  public XBooleanStatic connect( ExpressionContext exprContext, String driver, String dbURL, String user, String password )
  {
    try
    {
      Properties prop = new Properties();
      prop.put("user", user);
      prop.put("password", password);

      init(driver, dbURL, prop);

      return new XBooleanStatic(true);
    }
    catch(SQLException e)
    {
      setError(e,exprContext);
      return new XBooleanStatic(false);
    }
    catch (Exception e)
    {
      setError(e,exprContext);
      return new XBooleanStatic(false);
    }
  }


  /**
   * Create an XConnection object with a connection protocol
   * @param exprContext
   * @param driver JDBC driver of the form foo.bar.Driver.
   * @param dbURL database URL of the form jdbc:subprotocol:subname.
   * @param protocolElem list of string tag/value connection arguments,
   * normally including at least "user" and "password".
   *
   */
  public XBooleanStatic connect( ExpressionContext exprContext, String driver, String dbURL, Element protocolElem )
  {
    try
    {
      Properties prop = new Properties();

      NamedNodeMap atts = protocolElem.getAttributes();

      for (int i = 0; i < atts.getLength(); i++)
      {
        prop.put(atts.item(i).getNodeName(), atts.item(i).getNodeValue());
      }

      init(driver, dbURL, prop);

      return new XBooleanStatic(true);
    }
    catch(SQLException e)
    {
      setError(e,exprContext);
      return new XBooleanStatic(false);
    }
    catch (Exception e)
    {
      setError(e, exprContext);
      return new XBooleanStatic(false);
    }
  }


  /**
   * Allow the database connection information to be sepcified in
   * the XML tree. The connection information could also be
   * externally originated and passed in as an XSL Parameter.
   * The required XML Format is as follows.
   * A document fragment is needed to specify the connection information
   * the top tag name is not specific for this code, we are only interested
   * in the tags inside.
   * <DBINFO-TAG>
   * Specify the driver name for this connection pool
   * <dbdriver>drivername</dbdriver>
   * Specify the URL for the driver in this connection pool
   * <dburl>url</dburl>
   * Specify the password for this connection pool
   * <password>password</password>
   * Specify the username for this connection pool
   * <user>username</user>
   * You can add extra protocol items including the User Name & Password
   * with the protocol tag. For each extra protocol item, add a new element
   * where the name of the item is specified as the name attribute and
   * and its value as the elements value.
   * <protocol name="name of value">value</protocol>
   * </DBINFO-TAG>
   * @param e
   *
   * @throws SQLException
   */
  private void initFromElement( Element e )throws SQLException
  {

    Properties prop = new Properties();
    String driver = "";
    String dbURL = "";
    Node n = e.getFirstChild();

    if (null == n) return; // really need to throw an error

    do
    {
      String nName = n.getNodeName();

      if (nName.equalsIgnoreCase("dbdriver"))
      {
        driver = "";
        Node n1 = n.getFirstChild();
        if (null != n1)
        {
          driver = n1.getNodeValue();
        }
      }

      if (nName.equalsIgnoreCase("dburl"))
      {
        dbURL = "";
        Node n1 = n.getFirstChild();
        if (null != n1)
        {
          dbURL = n1.getNodeValue();
        }
      }

      if (nName.equalsIgnoreCase("password"))
      {
        String s = "";
        Node n1 = n.getFirstChild();
        if (null != n1)
        {
          s = n1.getNodeValue();
        }
        prop.put("password", s);
      }

      if (nName.equalsIgnoreCase("user"))
      {
        String s = "";
        Node n1 = n.getFirstChild();
        if (null != n1)
        {
          s = n1.getNodeValue();
        }
        prop.put("user", s);
      }

      if (nName.equalsIgnoreCase("protocol"))
      {
        String Name = "";

        NamedNodeMap attrs = n.getAttributes();
        Node n1 = attrs.getNamedItem("name");
        if (null != n1)
        {
          String s = "";
          Name = n1.getNodeValue();

          Node n2 = n.getFirstChild();
          if (null != n2) s = n2.getNodeValue();

          prop.put(Name, s);
        }
      }

    } while ( (n = n.getNextSibling()) != null);

    init(driver, dbURL, prop);
  }



  /**
   * Initilize is being called because we did not have an
   * existing Connection Pool, so let's see if we created one
   * already or lets create one ourselves.
   * @param driver
   * @param dbURL
   * @param prop
   *
   * @throws SQLException
   */
  private void init( String driver, String dbURL, Properties prop )throws SQLException
  {
    Connection con = null;

    if (DEBUG)
      System.out.println("XConnection, Connection Init");

    String user = prop.getProperty("user");
    if (user == null) user = "";

    String passwd = prop.getProperty("password");
    if (passwd == null) passwd = "";


    String poolName = driver + dbURL + user + passwd;
    ConnectionPool cpool = m_PoolMgr.getPool(poolName);

    if (cpool == null)
    {

      if (DEBUG)
      {
        System.out.println("XConnection, Creating Connection");
        System.out.println(" Driver  :" + driver);
        System.out.println(" URL     :" + dbURL);
        System.out.println(" user    :" + user);
        System.out.println(" passwd  :" + passwd);
      }


      DefaultConnectionPool defpool = new DefaultConnectionPool();

      if ((DEBUG) && (defpool == null))
        System.out.println("Failed to Create a Default Connection Pool");

      defpool.setDriver(driver);
      defpool.setURL(dbURL);
      defpool.setProtocol(prop);

      // Only enable pooling in the default pool if we are explicatly
      // told too.
      if (m_DefaultPoolingEnabled) defpool.setPoolEnabled(true);

      m_PoolMgr.registerPool(poolName, defpool);
      m_ConnectionPool = defpool;
    }
    else
    {
      m_ConnectionPool = cpool;
    }

    m_IsDefaultPool = true;

    //
    // Let's test to see if we really can connect
    // Just remember to give it back after the test.
    //
    try
    {
      con = m_ConnectionPool.getConnection();
    }
    catch(SQLException e)
    {
      if (con != null)
      {
        m_ConnectionPool.releaseConnectionOnError(con);
        con = null;
      }
      throw e;
    }
    finally
    {
      if ( con != null ) m_ConnectionPool.releaseConnection(con);
    }
  }

  /**
   * Allow the SQL Document to retrive a connection to be used
   * to build the SQL Statement.
   */
  public ConnectionPool getConnectionPool()
  {
    return m_ConnectionPool;
  }


  /**
   * Execute a query statement by instantiating an
   * @param exprContext
   * @param queryString the SQL query.
   * @return XStatement implements NodeIterator.
   * @throws SQLException
   * @link org.apache.xalan.lib.sql.XStatement XStatement}
   * object. The XStatement executes the query, and uses the result set
   * to create a
   * @link org.apache.xalan.lib.sql.RowSet RowSet},
   * a row-set element.
   */
  public DTM query( ExpressionContext exprContext, String queryString )
  {
    SQLDocument doc = null;

    try
    {
      if (DEBUG) System.out.println("pquery()");

      // Build an Error Document, NOT Connected.
      if ( null == m_ConnectionPool ) return null;

      SQLQueryParser query =
          m_QueryParser.parse
            (this, queryString, SQLQueryParser.NO_INLINE_PARSER);

      doc = SQLDocument.getNewDocument(exprContext);
      doc.execute(this, query);

      // also keep a local reference
      m_OpenSQLDocuments.addElement(doc);
    }
    catch (Exception e)
    {
      // OK We had an error building the document, let try and grab the
      // error information and clean up our connections.

      if (DEBUG) System.out.println("exception in query()");

      if (doc != null)
      {
        if (doc.hasErrors())
        {
          setError(e, doc, doc.checkWarnings());
        }

        doc.close(m_IsDefaultPool);
        doc = null;
      }
    }
    finally
    {
      if (DEBUG) System.out.println("leaving query()");
    }

    // Doc will be null if there was an error
    return doc;
  }

  /**
   * Execute a parameterized query statement by instantiating an
   * @param exprContext
   * @param queryString the SQL query.
   * @return XStatement implements NodeIterator.
   * @throws SQLException
   * @link org.apache.xalan.lib.sql.XStatement XStatement}
   * object. The XStatement executes the query, and uses the result set
   * to create a
   * @link org.apache.xalan.lib.sql.RowSet RowSet},
   * a row-set element.
   */
  public DTM pquery( ExpressionContext exprContext, String queryString )
  {
    return(pquery(exprContext, queryString, null));
  }

  /**
   * Execute a parameterized query statement by instantiating an
   * @param exprContext
   * @param queryString the SQL query.
   * @param typeInfo
   * @return XStatement implements NodeIterator.
   * @throws SQLException
   * @link org.apache.xalan.lib.sql.XStatement XStatement}
   * object. The XStatement executes the query, and uses the result set
   * to create a
   * @link org.apache.xalan.lib.sql.RowSet RowSet},
   * a row-set element.
   * This method allows for the user to pass in a comma seperated
   * String that represents a list of parameter types. If supplied
   * the parameter types will be used to overload the current types
   * in the current parameter list.
   */
  public DTM pquery( ExpressionContext exprContext, String queryString, String typeInfo)
  {
    SQLDocument doc = null;

    try
    {
      if (DEBUG) System.out.println("pquery()");

      // Build an Error Document, NOT Connected.
      if ( null == m_ConnectionPool ) return null;

      SQLQueryParser query =
          m_QueryParser.parse
            (this, queryString, SQLQueryParser.NO_OVERRIDE);

      // If we are not using the inline parser, then let add the data
      // to the parser so it can populate the SQL Statement.
      if ( !m_InlineVariables )
      {
        addTypeToData(typeInfo);
        query.setParameters(m_ParameterList);
      }

      doc = SQLDocument.getNewDocument(exprContext);
      doc.execute(this, query);

      // also keep a local reference
      m_OpenSQLDocuments.addElement(doc);
    }
    catch (Exception e)
    {
      // OK We had an error building the document, let try and grab the
      // error information and clean up our connections.

      if (DEBUG) System.out.println("exception in query()");

      if (doc != null)
      {
        if (doc.hasErrors())
        {
          setError(e, doc, doc.checkWarnings());
        }

        // If we are using the Default Connection Pool, then
        // force the connection pool to flush unused connections.
        doc.close(m_IsDefaultPool);
        doc = null;
      }
    }
    finally
    {
      if (DEBUG) System.out.println("leaving query()");
    }

    // Doc will be null if there was an error
    return doc;
  }

  /**
   * The purpose of this routine is to force the DB cursor to skip forward
   * N records. You should call this function after [p]query to help with
   * pagination. i.e. Perfrom your select, then skip forward past the records
   * you read previously.
   * 
   * @param exprContext
   * @param o
   * @param value
   */
  public void skipRec( ExpressionContext exprContext, Object o, int value )
  {
    SQLDocument sqldoc = null;
    DTMNodeIterator nodei = null;
      
    sqldoc = locateSQLDocument( exprContext, o);
    if (sqldoc != null) sqldoc.skip(value);
  }

  

  private void addTypeToData(String typeInfo)
  {
      int indx;

      if ( typeInfo != null && m_ParameterList != null )
      {
          // Parse up the parameter types that were defined
          // with the query
          StringTokenizer plist = new StringTokenizer(typeInfo);

          // Override the existing type that is stored in the
          // parameter list. If there are more types than parameters
          // ignore for now, a more meaningfull error should occur
          // when the actual query is executed.
          indx = 0;
          while (plist.hasMoreTokens())
          {
            String value = plist.nextToken();
            QueryParameter qp = (QueryParameter) m_ParameterList.elementAt(indx);
            if ( null != qp )
            {
              qp.setTypeName(value);
            }

            indx++;
          }
      }
  }

  /**
   * Add an untyped value to the parameter list.
   * @param value
   *
   */
  public void addParameter( String value )
  {
    addParameterWithType(value, null);
  }

  /**
   * Add a typed parameter to the parameter list.
   * @param value
   * @param Type
   *
   */
  public void addParameterWithType( String value, String Type )
  {
    m_ParameterList.addElement( new QueryParameter(value, Type) );
  }


  /**
   * Add a single parameter to the parameter list
   * formatted as an Element
   * @param e
   *
   */
  public void addParameterFromElement( Element e )
  {
    NamedNodeMap attrs = e.getAttributes();
    Node Type = attrs.getNamedItem("type");
    Node n1  = e.getFirstChild();
    if (null != n1)
    {
      String value = n1.getNodeValue();
      if (value == null) value = "";
      m_ParameterList.addElement( new QueryParameter(value, Type.getNodeValue()) );
    }
  }


  /**
   * Add a section of parameters to the Parameter List
   * Do each element from the list
   * @param nl
   *
   */
  public void addParameterFromElement( NodeList nl )
  {
    //
    // Each child of the NodeList represents a node
    // match from the select= statment. Process each
    // of them as a seperate list.
    // The XML Format is as follows
    //
    // <START-TAG>
    //   <TAG1 type="int">value</TAG1>
    //   <TAGA type="int">value</TAGA>
    //   <TAG2 type="string">value</TAG2>
    // </START-TAG>
    //
    // The XSL to process this is formatted as follows
    // <xsl:param name="plist" select="//START-TAG" />
    // <sql:addParameter( $plist );
    //
    int count = nl.getLength();
    for (int x=0; x<count; x++)
    {
      addParameters( (Element) nl.item(x));
    }
  }

  /**
   * @param elem
   *
   */
  private void addParameters( Element elem )
  {
    //
    // Process all of the Child Elements
    // The format is as follows
    //
    //<TAG type ="typeid">value</TAG>
    //<TAG1 type ="typeid">value</TAG1>
    //<TAGA type ="typeid">value</TAGA>
    //
    // The name of the Node is not important just is value
    // and if it contains a type attribute

    Node n = elem.getFirstChild();

    if (null == n) return;

    do
    {
      if (n.getNodeType() == Node.ELEMENT_NODE)
      {
        NamedNodeMap attrs = n.getAttributes();
        Node Type = attrs.getNamedItem("type");
        String TypeStr;

        if (Type == null) TypeStr = "string";
        else TypeStr = Type.getNodeValue();

        Node n1  = n.getFirstChild();
        if (null != n1)
        {
          String value = n1.getNodeValue();
          if (value == null) value = "";


          m_ParameterList.addElement(
            new QueryParameter(value, TypeStr) );
        }
      }
    } while ( (n = n.getNextSibling()) != null);
  }

  /**
   *
   */
  public void clearParameters( )
  {
    m_ParameterList.removeAllElements();
  }

  /**
   * There is a problem with some JDBC drivers when a Connection
   * is open and the JVM shutsdown. If there is a problem, there
   * is no way to control the currently open connections in the
   * pool. So for the default connection pool, the actuall pooling
   * mechinsm is disabled by default. The Stylesheet designer can
   * re-enabled pooling to take advantage of connection pools.
   * The connection pool can even be disabled which will close all
   * outstanding connections.
   *
   * @deprecated Use setFeature("default-pool-enabled", "true");
   */
  public void enableDefaultConnectionPool( )
  {

    if (DEBUG)
      System.out.println("Enabling Default Connection Pool");

    m_DefaultPoolingEnabled = true;

    if (m_ConnectionPool == null) return;
    if (m_IsDefaultPool) return;

    m_ConnectionPool.setPoolEnabled(true);

  }

  /**
   * See enableDefaultConnectionPool
   *
   * @deprecated Use setFeature("default-pool-enabled", "false");
   */
  public void disableDefaultConnectionPool( )
  {
    if (DEBUG)
      System.out.println("Disabling Default Connection Pool");

    m_DefaultPoolingEnabled = false;

    if (m_ConnectionPool == null) return;
    if (!m_IsDefaultPool) return;

    m_ConnectionPool.setPoolEnabled(false);
  }


  /**
   * Control how the SQL Document uses memory. In Streaming Mode,
   * memory consumption is greatly reduces so you can have queries
   * of unlimited size but it will not let you traverse the data
   * more than once.
   *
   * @deprecated Use setFeature("streaming", "true");
   */
  public void enableStreamingMode( )
  {

    if (DEBUG)
      System.out.println("Enabling Streaming Mode");

    m_IsStreamingEnabled = true;
  }

  /**
   * Control how the SQL Document uses memory. In Streaming Mode,
   * memory consumption is greatly reduces so you can have queries
   * of unlimited size but it will not let you traverse the data
   * more than once.
   *
   * @deprecated Use setFeature("streaming", "false");
   */
  public void disableStreamingMode( )
  {

    if (DEBUG)
      System.out.println("Disable Streaming Mode");

    m_IsStreamingEnabled = false;
  }

  /**
   * Provide access to the last error that occued. This error
   * may be over written when the next operation occurs.
   *
   */
  public DTM getError( )
  {
    if ( m_FullErrors )
    {
      for ( int idx = 0 ; idx < m_OpenSQLDocuments.size() ; idx++ )
      {
        SQLDocument doc = (SQLDocument)m_OpenSQLDocuments.elementAt(idx);
        SQLWarning warn = doc.checkWarnings();
        if ( warn != null ) setError(null, doc, warn);
      }
    }

    return(buildErrorDocument());
  }

  /**
   * Close the connection to the data source.
   *
   * @throws SQLException
   */
  public void close( )throws SQLException
  {
    if (DEBUG)
      System.out.println("Entering XConnection.close()");

    //
    // This function is included just for Legacy support
    // If it is really called then we must me using a single
    // document interface, so close all open documents.

    while(m_OpenSQLDocuments.size() != 0)
    {
      SQLDocument d = (SQLDocument) m_OpenSQLDocuments.elementAt(0);
      try
      {
        // If we are using the Default Connection Pool, then
        // force the connection pool to flush unused connections.
        d.close(m_IsDefaultPool);
      }
      catch (Exception se ) {}

      m_OpenSQLDocuments.removeElementAt(0);
    }

    if ( null != m_Connection )
    {
      m_ConnectionPool.releaseConnection(m_Connection);
      m_Connection = null;
    }

    if (DEBUG)
      System.out.println("Exiting XConnection.close");
  }

  /**
   * Close the connection to the data source. Only close the connections
   * for a single document.
   *
   * @throws SQLException
   */

  public void close(ExpressionContext exprContext, Object doc) throws SQLException 
  {
    if (DEBUG)
        System.out.println("Entering XConnection.close(" + doc + ")");

    SQLDocument sqlDoc = locateSQLDocument(exprContext, doc);
    if (sqlDoc != null)
    {
      // If we are using the Default Connection Pool, then
      // force the connection pool to flush unused connections.
      sqlDoc.close(m_IsDefaultPool);
      m_OpenSQLDocuments.remove(sqlDoc);
    } 
  }


  /**
   * When an SQL Document is returned as a DTM object, the XSL variable is actually 
   * assigned as a DTMIterator. This is a helper function that will allow you to get
   * a reference to the original SQLDocument from the iterator.
   * 
   * Original code submitted by 
   *  Moraine Didier mailto://didier.moraine@winterthur.be
   * @param doc
   * @return
   */
  private SQLDocument locateSQLDocument(ExpressionContext exprContext, Object doc)
  {
    try
    {
      if (doc instanceof DTMNodeIterator)
      {
        DTMNodeIterator dtmIter = (DTMNodeIterator)doc;
        try
        {
          DTMNodeProxy root = (DTMNodeProxy)dtmIter.getRoot();
          return (SQLDocument) root.getDTM();
        }
        catch (Exception e)
        {
          XNodeSet xNS = (XNodeSet)dtmIter.getDTMIterator();
          DTMIterator iter = (DTMIterator)xNS.getContainedIter();
          DTM dtm = iter.getDTM(xNS.nextNode());
          return (SQLDocument)dtm;
        }
      }

/*
      XNodeSet xNS = (XNodeSet)dtmIter.getDTMIterator();
      OneStepIterator iter = (OneStepIterator)xNS.getContainedIter();
      DTMManager aDTMManager = (DTMManager)iter.getDTMManager();
      return (SQLDocument)aDTMManager.getDTM(xNS.nextNode());
*/
      setError(new Exception("SQL Extension:close - Can Not Identify SQLDocument"), exprContext);    
      return null;  
    }
    catch(Exception e)
    {
      setError(e, exprContext);
      return null;
    }
  }
  
  /**
   * @param exprContext
   * @param excp
   *
   */
  private SQLErrorDocument buildErrorDocument()
  {
    SQLErrorDocument eDoc = null;

    if ( m_LastSQLDocumentWithError != null)
    {
      // @todo
      // Do we need to do something with this ??
      //    m_Error != null || (m_FullErrors && m_Warning != null) )

      ExpressionContext ctx = m_LastSQLDocumentWithError.getExpressionContext();
      SQLWarning        warn = m_LastSQLDocumentWithError.checkWarnings();


      try
      {
        DTMManager mgr =
          ((XPathContext.XPathExpressionContext)ctx).getDTMManager();
        DTMManagerDefault mgrDefault = (DTMManagerDefault) mgr;
        int dtmIdent = mgrDefault.getFirstFreeDTMID();

        eDoc = new SQLErrorDocument(
            mgr, dtmIdent<<DTMManager.IDENT_DTM_NODE_BITS,
            m_Error, warn, m_FullErrors);

        // Register our document
        mgrDefault.addDTM(eDoc, dtmIdent);

        // Clear the error and warning.
        m_Error = null;
        m_LastSQLDocumentWithError = null;
      }
      catch(Exception e)
      {
        eDoc = null;
      }
    }

    return(eDoc);
  }


  /**
   * This is an internal version of Set Error that is called withen
   * XConnection where there is no SQLDocument created yet. As in the
   * Connect statement or creation of the ConnectionPool.
   */
  public void setError(Exception excp,ExpressionContext expr)
  {
    try
    {
      ErrorListener listen = expr.getErrorListener();
      if ( listen != null && excp != null )
      {
        
        listen.warning(
          new TransformerException(excp.toString(),
          expr.getXPathContext().getSAXLocator(), excp));
      }
    }
    catch(Exception e) {}
  }

  /**
   * Set an error and/or warning on this connection.
   *
   */
  public void setError(Exception excp, SQLDocument doc, SQLWarning warn)
  {

    ExpressionContext cont = doc.getExpressionContext();
    m_LastSQLDocumentWithError = doc;

    try
    {
      ErrorListener listen = cont.getErrorListener();
      if ( listen != null && excp != null )
      listen.warning(
        new TransformerException(excp.toString(),
        cont.getXPathContext().getSAXLocator(), excp));

      if ( listen != null && warn != null )
      {
        listen.warning(new TransformerException(
          warn.toString(), cont.getXPathContext().getSAXLocator(), warn));
      }

      // Assume there will be just one error, but perhaps multiple warnings.
      if ( excp != null )  m_Error = excp;

      if ( warn != null )
      {
        // Because the log may not have processed the previous warning yet
        // we need to make a new one.
        SQLWarning tw =
          new SQLWarning(warn.getMessage(), warn.getSQLState(),
            warn.getErrorCode());
        SQLWarning nw = warn.getNextWarning();
        while ( nw != null )
        {
          tw.setNextWarning(new SQLWarning(nw.getMessage(),
            nw.getSQLState(), nw.getErrorCode()));

          nw = nw.getNextWarning();
        }

        tw.setNextWarning(
          new SQLWarning(warn.getMessage(), warn.getSQLState(),
            warn.getErrorCode()));

//        m_Warning = tw;

      }
    }
    catch(Exception e)
    {
      //m_Error = null;
    }
  }

  /**
   * Set feature options for this XConnection.
   * @param feature The name of the feature being set, currently supports (streaming, inline-variables, multiple-results, cache-statements, default-pool-enabled).
   * @param setting The new setting for the specified feature, currently "true" is true and anything else is false.
   *
   */
  public void setFeature(String feature, String setting)
  {
    boolean value = false;

    if ( "true".equalsIgnoreCase(setting) ) value = true;

    if ( "streaming".equalsIgnoreCase(feature) )
    {
      m_IsStreamingEnabled = value;
    }
    else if ( "inline-variables".equalsIgnoreCase(feature) )
    {
      m_InlineVariables = value;
    }
    else if ( "multiple-results".equalsIgnoreCase(feature) )
    {
      m_IsMultipleResultsEnabled = value;
    }
    else if ( "cache-statements".equalsIgnoreCase(feature) )
    {
      m_IsStatementCachingEnabled = value;
    }
    else if ( "default-pool-enabled".equalsIgnoreCase(feature) )
    {
      m_DefaultPoolingEnabled = value;

      if (m_ConnectionPool == null) return;
      if (m_IsDefaultPool) return;

      m_ConnectionPool.setPoolEnabled(value);
    }
    else if ( "full-errors".equalsIgnoreCase(feature) )
    {
      m_FullErrors = value;
    }
  }

  /**
   * Get feature options for this XConnection.
   * @param feature The name of the feature to get the setting for.
   * @return The setting of the specified feature. Will be "true" or "false" (null if the feature is not known)
   */
  public String getFeature(String feature)
  {
    String value = null;

    if ( "streaming".equalsIgnoreCase(feature) )
      value = m_IsStreamingEnabled ? "true" : "false";
    else if ( "inline-variables".equalsIgnoreCase(feature) )
      value = m_InlineVariables ? "true" : "false";
    else if ( "multiple-results".equalsIgnoreCase(feature) )
      value = m_IsMultipleResultsEnabled ? "true" : "false";
    else if ( "cache-statements".equalsIgnoreCase(feature) )
      value = m_IsStatementCachingEnabled ? "true" : "false";
    else if ( "default-pool-enabled".equalsIgnoreCase(feature) )
      value = m_DefaultPoolingEnabled ? "true" : "false";
    else if ( "full-errors".equalsIgnoreCase(feature) )
      value = m_FullErrors ? "true" : "false";

    return(value);
  }



  /**
   *
   */
  protected void finalize( )
  {
    if (DEBUG) System.out.println("In XConnection, finalize");
    try
    {
      close();
    }
    catch(Exception e)
    {
      // Empty We are final Anyway
    }
  }

}
