/*
 * The Apache Software License, Version 1.1
 *
 *
 * Copyright (c) 1999 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "Xalan" and "Apache Software Foundation" must
 *    not be used to endorse or promote products derived from this
 *    software without prior written permission. For written
 *    permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache",
 *    nor may "Apache" appear in their name, without prior written
 *    permission of the Apache Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation and was
 * originally based on software copyright (c) 1999, Lotus
 * Development Corporation., http://www.lotus.com.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 */
package org.apache.xalan.xslt.extensions;

import org.apache.xalan.xpath.*;
import org.apache.xalan.xpath.xml.*;
import org.w3c.dom.*;
import org.apache.xerces.dom.*;

import java.util.*;
import java.sql.*;

/**
 * RowSetLocator uses JDBC to connect to a database, execute a query, and return
 * a result set, then returns a lazy row-set DOM. This class can ONLY be used with
 * the Xerces liaison (org.apache.xalan.xpath.xdom.XercesLiaison) and Xerces XML parser,
 * since it extends the Xerces DOM implementation to create a lazy row-set DOM.
 */
public class RowSetLocator extends SimpleNodeLocator
{
  /**
   * Create a SimpleNodeLocator object.
   */
  public RowSetLocator(String driver, String dbURL)
  {
    super();
    init(driver, dbURL);
  }

  /**
   * A JDBC driver of the form "foo.bar.Driver".
   */
  public String m_driver;

  /**
   * A database URL of the form jdbc:subprotocol:subname.
   */
  public String m_dbURL;

  /**
   * Connection properties
   */
  static java.util.Properties m_protocol = new Properties();

  /**
   * User ID
   */
  public String m_userID = "";
   /**
   * Password
   */
  public String m_password = "";

  /**
   * A list of arbitrary string tag/value pairs as connection
   * arguments; normally at least a "user" and "password"
   * property should be included.
   */

  public static void protocol(org.apache.xalan.xslt.XSLProcessorContext context,
                   org.apache.xalan.xslt.ElemExtensionCall protocolElem)
  {
    org.xml.sax.AttributeList atts = protocolElem.getAttributeList(); // added dml.
    int ln = atts.getLength();
    for (int i = 0; i < ln; i++)
    {
      m_protocol.put(atts.getName(i), atts.getValue(i));
    }
  }

  /**
   * The JDBC connection.
   */
  public Connection m_connection = null;

  /**
   * The SQL statement, which lets us execute commands against the connection.
   */
  Statement m_statement = null;

  /**
   * Initialize.
   */
  private void init(String driver, String dbURL)
  {
    m_driver = driver;
    m_dbURL = dbURL;
    // System.out.println("init completed");
  }

  /**
   * Execute the proprietary connect() function, which returns an
   * instance of XLocator.  When the XPath object sees a return type
   * of XLocator, it calls the locationPath function that passes
   * in the connectArgs.  The opPos and args params are not used
   * by this function.  This really is just a factory function
   * for the XLocator instance, but this fact is hidden from the
   * user.
   * @param driver JDBC driver.
   * @param dbURL database URL of the form jdbc:subprotocol:subname.
   * @param sqlQuery typically a static SQL SELECT statement, which
   * is normally executed by the connectToNodes function when called by the XPath object.
   * @returns An XLocator object.
   */
  // coming public static XLocator connect (String driver, String dbURL, String sqlQuery) rest is in m_protocol
  public static XLocator connect (String driver,
                                  String dbURL,
                                  String sqlQuery)
  {
    RowSetLocator locator = null;
    try
    {
      locator = new RowSetLocator(driver, dbURL);
      // The driver is installed by loading its class.
      Class.forName(locator.m_driver);
      // System.out.println("about to connect");
      locator.m_connection = DriverManager.getConnection(locator.m_dbURL, m_protocol);
      // System.out.println("connection OK");
      /*
      We could also turn autocommit off by putting
      ;autocommit=false on the URL.
      */
      locator.m_connection.setAutoCommit(false);
      // Creating a statement lets us issue commands against
      // the connection.
      locator.m_statement = locator.m_connection.createStatement();
      // System.out.println("ready to return XLocator");
    }
    catch (Throwable e)
    {
      e.printStackTrace();
    }
    return locator;
  }


  /**
   * Execute a connection and process the LocationPath,
   * The arguments to the static connect function
   * are re-passed to this function.
   * @param xpath The xpath that is executing.
   * @param context The current source tree context node.
   * @param opPos The current position in the xpath.m_opMap array.
   * @param connectArgs The same arguments that were passed to the
   * static connect function.
   * @returns the result of the query in an XNodeSet object.
   */
  public XNodeSet connectToNodes(XPath xpath, XPathSupport execContext, Node context,
                               int opPos, Vector connectArgs)
  {
    // System.out.println("connectToNodes called");
    XNodeSet results = new XNodeSet(); //create empty XNodeSet; was new XNodeSet(xpath.m_callbacks)
    MutableNodeList mnl = results.mutableNodeset();
    // RowSetLocator locator = null;
    try
    {
      // Select the rows.
      String query = ((XObject)connectArgs.elementAt(2)).str();
      ResultSet rowSet
        = m_statement.executeQuery(query);
      // System.out.println("query executed");

      DocumentImpl doc = new DocumentImpl();
      Element elem = new RowSetElem(doc, "row-set", rowSet, this);
      doc.appendChild(elem);
      if((xpath.OP_LOCATIONPATH == xpath.getOpMap()[opPos]))
      {
        XNodeSet xnl = xpath.locationPath(execContext, doc, opPos, null, null, false);
        /* locationPath args were (doc, opPos); now takes 6 args
                              (XPathSupport execContext,
                               Node context, int opPos,
                               NodeCallback callback, Object callbackInfo,
                               boolean stopAtFirst) */

        if(null != xnl)
        {
          mnl.addNodes(xnl.nodeset());
          execContext.associateXLocatorToNode(doc, this); // was XPath method
        }
      }
      else
      {
        mnl.addNode(elem);
        execContext.associateXLocatorToNode(doc, this);   // was XPath method
      }
    }
    catch (Throwable e)
    {
      e.printStackTrace();
    }

    return results;
  }

  /**
   * The RowSetElem extends the behavior of ElementImpl
   * by lazily evaluating it's row children.
   * TODO: We'll need some sort of garbage collection for
   * large rowsets, so we can discard rows at the beginning
   * of the child list, or whatever.
   */
  class RowSetElem extends ElementImpl
  {
    /**
     * The query result set.
     */
    ResultSet m_rowSet;

    /**
     * The owning RowSetLocator.
     */
    RowSetLocator m_locator;

    public RowSetElem(DocumentImpl ownerDoc, String name,
                      ResultSet rowSet, RowSetLocator locator)
    {
      super(ownerDoc, name);
      m_rowSet = rowSet;
      m_locator = locator;

      try
      {
        ResultSetMetaData metadata = m_rowSet.getMetaData();

        if(null != metadata)
        {
          int nColumns = metadata.getColumnCount();
          setAttribute("n-columns", Integer.toString( nColumns ));
          for(int i = 1; i <= nColumns; i++)
          {
            Element colHeader = ownerDoc.createElement("col-header");
            appendChild(colHeader);

            {
              String tableName = metadata.getTableName(i);
              if(tableName.length() > 0)
                colHeader.setAttribute("table-name", tableName);
            }
            {
              String catalog = metadata.getCatalogName(i);
              if(catalog.length() > 0)
                colHeader.setAttribute("catalog", catalog);
            }
            {
              String columnNname = metadata.getColumnName(i);
              if(null != columnNname)
              {
                if(columnNname.length() > 0)
                  colHeader.setAttribute("column-name", columnNname);
              }
            }
            {
              String label = metadata.getColumnLabel(i);
              if(null != label)
              {
                if(label.length() > 0)
                  colHeader.setAttribute("column-label", label);
              }
            }
            {
              int displaySize = metadata.getColumnDisplaySize(i);
              colHeader.setAttribute("display-size", Integer.toString(displaySize));
            }
            {
              int precision = metadata.getPrecision(i);
              colHeader.setAttribute("precision", Integer.toString(precision));
            }
            {
              String type = metadata.getColumnTypeName(i);
              if(type.length() > 0)
                colHeader.setAttribute("type", type);
            }
            {
              String schema = metadata.getSchemaName(i);
              if(schema.length() > 0)
                colHeader.setAttribute("schema", schema);
            }
          }
        }
        else
        {
          System.out.println("Could not get metadata!!");
        }
      }
      catch (Throwable e)
      {
        e.printStackTrace();
      }
    }

    /**
     * Test whether this node has any children. Convenience shorthand
     * for (Node.getFirstChild()!=null)
     */
    public boolean hasChildNodes()
    {
      return getFirstChild() != null;
    }

    /** The first child of this Node, or null if none. */
    public Node getFirstChild()
    {
      while ((m_count <= 1) && createRow())
      {
        ;
      }
      return super.getFirstChild();

    } // getFirstChild():Node

    /** The last child of this Node, or null if none. */
    public Node getLastChild()
    {
      getLength();
      return super.getLastChild();
    } // getLastChild():Node

    int m_count = 0;

    /**
     * NodeList method: Count the immediate children of this node.
     * (Expensive, so try to avoid using).
     * @return int
     */
    public int getLength()
    {
      // System.out.println("getLength called: "+m_count);
      while (createRow()) // Creates a RowElem and increments m_count.
      {
        ;
      }
      // System.out.println("counted: "+m_count);
      return m_count;
    } // getLength():int

    /**
     * Create a row child. For now, just create all the columns,
     * don't try to lazily evaluate.
     */
    boolean createRow()
    {
      boolean didCreate = false;
      try
      {
        didCreate = m_rowSet.next();
        if(didCreate)
        {
          ResultSetMetaData metadata = m_rowSet.getMetaData();
          Document doc = this.getOwnerDocument();
          Element row = new RowElem( (DocumentImpl)doc, "row");
          this.appendChild(row);
          int nColumns = metadata.getColumnCount();
          for(int i = 1; i <= nColumns; i++)
          {
            Element col = doc.createElement("col");
            row.appendChild(col);
            String data = m_rowSet.getString(i);
            Text text = doc.createTextNode(data);
            col.appendChild(text);
          }
          m_count++;
        }
      }
      catch (Throwable e)
      {
        e.printStackTrace();
      }
      return didCreate;
    }

    /**
     * NodeList method: Return the Nth immediate child of this node, or
     * null if the index is out of bounds.
     * @return org.w3c.dom.Node
     * @param Index int
     */
    public Node item(int index)
    {
      while ((m_count <= index) && createRow())
      {
        ;
      }
      return super.item(index);
    } // item(int):Node
  }

  /**
   * Set up a derived class for rows to override getNextSibling.
   */
  class RowElem extends ElementImpl
  {
    public RowElem(DocumentImpl ownerDoc, String name)
    {
      super(ownerDoc, name);
    }

    /** The next child of this node's parent, or null if none */
    public Node getNextSibling()
    {
      Node next = super.getNextSibling();
      if(null == next) // Row has not yet been created.
      {
        RowSetElem rsElem = (RowSetElem)getParentNode();
        rsElem.createRow();
        next = super.getNextSibling();
      }
      return next;
    }
  }

}

