/* * 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.lib.sql; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.DriverManager; import java.sql.SQLException; import java.util.Properties; import java.util.Vector; import java.util.StringTokenizer; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.traversal.NodeIterator; import org.w3c.dom.NodeList; import org.w3c.dom.Node; /** * An XSLT extension that allows a stylesheet to * access JDBC data. 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 JDBC connection. */ public Connection m_connection = null; /** * Reference to the ConnectionPool used */ private ConnectionPool m_ConnectionPool = null; private String m_ConnectionPoolName; private boolean m_IsDefaultPool = false; private boolean m_DefaultPoolingEnabled = false; /** * 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 XConnectionPoolManager m_PoolMgr = new XConnectionPoolManager(); /** * For PreparedStatements, we need a place to * to store the parameters in a vector. */ public Vector m_ParameterList = new Vector(); // The original constructors will be kept around for backwards // compatibility. Future Stylesheets should use the approaite // connect method to receive full error information. // public XConnection (String ConnPoolName) { connect(ConnPoolName); } public XConnection(String driver, String dbURL) { connect(driver, dbURL); } public XConnection(NodeList list) { connect(list); } public XConnection(String driver, String dbURL, String user, String password) { connect(driver, dbURL, user, password); } public XConnection(String driver, String dbURL, Element protocolElem) { connect(driver, dbURL, protocolElem); } /** * * Create an XConnection using the name of an existing Connection Pool * @param <code>String poolName</code>, name of the existing pool * to pull connections from. * */ public NodeIterator connect(String ConnPoolName) { try { if ((m_ConnectionPool != null) && (m_IsDefaultPool)) m_PoolMgr.removePool(m_ConnectionPoolName); m_ConnectionPool = m_PoolMgr.getPool(ConnPoolName); m_ConnectionPoolName = ConnPoolName; m_connection = m_ConnectionPool.getConnection(); } catch(SQLException e) { SQLExtensionError err = new SQLExtensionError(e); return err; } return null; } /** * Create an XConnection object with just a driver and database URL. * @param driver JDBC driver of the form foo.bar.Driver. * @param dbURL database URL of the form jdbc:subprotocol:subname. */ public NodeIterator connect(String driver, String dbURL) { try { init(driver, dbURL, new Properties() ); } catch(SQLException e) { SQLExtensionError err = new SQLExtensionError(e); return err; } catch (Exception e) { ExtensionError err = new ExtensionError(e); return err; } return null; } public NodeIterator connect(Element protocolElem) { try { initFromElement(protocolElem); } catch(SQLException e) { SQLExtensionError err = new SQLExtensionError(e); return err; } catch (Exception e) { ExtensionError err = new ExtensionError(e); return err; } return null; } public NodeIterator connect(NodeList list) { try { initFromElement( (Element) list.item(0) ); } catch(SQLException e) { SQLExtensionError err = new SQLExtensionError(e); return err; } catch (Exception e) { ExtensionError err = new ExtensionError(e); return err; } return null; } /** * Create an XConnection object with user ID and password. * @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 NodeIterator connect(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); } catch(SQLException e) { SQLExtensionError err = new SQLExtensionError(e); return err; } catch (Exception e) { ExtensionError err = new ExtensionError(e); return err; } return null; } /** * Create an XConnection object with a connection protocol * @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 NodeIterator connect(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); } catch(SQLException e) { SQLExtensionError err = new SQLExtensionError(e); return err; } catch (Exception e) { ExtensionError err = new ExtensionError(e); return err; } return null; } /** * * 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> * */ 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 JDBC driver of the form foo.bar.Driver. * @param dbURL database URL of the form jdbc:subprotocol:subname. * @param Properties list of string tag/value connection arguments, * normally including at least "user" and "password". * @param getConnectionArgs Connection arguments */ private void init(String driver, String dbURL, Properties prop) throws SQLException { 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); // We are limited to only one Default Connection Pool // at a time. // If we are creating a new Default Pool, release the first // One so it is not lost when we close the Stylesheet if ( (m_ConnectionPool != null) && (m_IsDefaultPool) && (cpool != m_ConnectionPool)) { m_PoolMgr.removePool(m_ConnectionPoolName); } 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.enablePool(); m_PoolMgr.registerPool(poolName, defpool); m_ConnectionPool = defpool; m_ConnectionPoolName = poolName; } else { if (DEBUG) System.out.println("Default Connection already existed"); m_ConnectionPool = cpool; m_ConnectionPoolName = poolName; } m_ConnectionPool.testConnection(); m_connection = m_ConnectionPool.getConnection(); } /** * Execute a query statement by instantiating an * {@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. * * @param queryString the SQL query. * @return XStatement implements NodeIterator. * * @throws SQLException */ public NodeIterator query(String queryString) { try { return new XStatement(this, queryString); } catch(SQLException e) { if (DEBUG) { System.out.println("SQL Exception in Query"); e.printStackTrace(); } SQLExtensionError err = new SQLExtensionError(e); return err; } catch (Exception e) { if (DEBUG) { System.out.println("Exception in Query"); e.printStackTrace(); } ExtensionError err = new ExtensionError(e); return err; } } /** * Execute a parameterized query statement by instantiating an * {@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. * * @param queryString the SQL query. * @return XStatement implements NodeIterator. * * @throws SQLException */ public NodeIterator pquery(String queryString) { try { return new XStatement(this, queryString, m_ParameterList); } catch(SQLException e) { SQLExtensionError err = new SQLExtensionError(e); return err; } catch (Exception e) { ExtensionError err = new ExtensionError(e); return err; } } /** * Execute a parameterized query statement by instantiating an * {@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. * * @param queryString the SQL query. * @return XStatement implements NodeIterator. * * @throws SQLException */ public NodeIterator pquery(String queryString, String typeInfo) { try { int indx = 0; QueryParameter param = 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. while (plist.hasMoreTokens()) { String value = plist.nextToken(); param = (QueryParameter) m_ParameterList.elementAt(indx); if ( null != param ) { param.setType(value); } } return new XStatement(this, queryString, m_ParameterList); } catch(SQLException e) { SQLExtensionError err = new SQLExtensionError(e); return err; } catch (Exception e) { ExtensionError err = new ExtensionError(e); return err; } } /** * Add an untyped value to the parameter list. */ public void addParameter(String value) { addParameterWithType(value, null); } /** * Add a typed parameter to the parameter list. */ 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 */ 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 */ 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)); } } 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); } /** * * 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. * * */ 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.enablePool(); } /** * See enableDefaultConnectionPool */ 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.disablePool(); } /** * Close the connection to the data source. * * * @throws SQLException */ public void close() throws SQLException { if (DEBUG) System.out.println("Entering XConnection.close"); if (null != m_connection) { if (null != m_ConnectionPool) { m_ConnectionPool.releaseConnection(m_connection); } else { // something is wrong here, we have a connection // but no controlling pool, close it anyway the // error will show up as an excpeion elsewhere m_connection.close(); } } m_connection = null; if (DEBUG) System.out.println("Exiting XConnection.close"); } protected void finalize() { if (DEBUG) System.out.println("In XConnection, finalize"); } }