001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements. See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership. The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the  "License");
007     * you may not use this file except in compliance with the License.
008     * You may obtain a copy of the License at
009     *
010     *     http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing, software
013     * distributed under the License is distributed on an "AS IS" BASIS,
014     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015     * See the License for the specific language governing permissions and
016     * limitations under the License.
017     */
018    /*
019     * $Id: XConnection.java 468638 2006-10-28 06:52:06Z minchau $
020     */
021    package org.apache.xalan.lib.sql;
022    
023    import java.sql.Connection;
024    import java.sql.SQLException;
025    import java.sql.SQLWarning;
026    import java.util.Properties;
027    import java.util.StringTokenizer;
028    import java.util.Vector;
029    
030    import javax.xml.transform.ErrorListener;
031    import javax.xml.transform.TransformerException;
032    
033    import org.apache.xalan.extensions.ExpressionContext;
034    import org.apache.xml.dtm.DTM;
035    import org.apache.xml.dtm.DTMIterator;
036    import org.apache.xml.dtm.DTMManager;
037    import org.apache.xml.dtm.ref.DTMManagerDefault;
038    import org.apache.xml.dtm.ref.DTMNodeIterator;
039    import org.apache.xml.dtm.ref.DTMNodeProxy;
040    import org.apache.xpath.XPathContext;
041    import org.apache.xpath.objects.XBooleanStatic;
042    import org.apache.xpath.objects.XNodeSet;
043    import org.w3c.dom.Element;
044    import org.w3c.dom.NamedNodeMap;
045    import org.w3c.dom.Node;
046    import org.w3c.dom.NodeList;
047    
048    /**
049     * An XSLT extension that allows a stylesheet to
050     * access JDBC data.
051     *
052     * It is accessed by specifying a namespace URI as follows:
053     * <pre>
054     *    xmlns:sql="http://xml.apache.org/xalan/sql"
055     * </pre>
056     *
057     * From the stylesheet perspective,
058     * XConnection provides 3 extension functions: new(),
059     * query(), and close().
060     * Use new() to call one of XConnection constructors, which
061     * establishes a JDBC driver connection to a data source and
062     * returns an XConnection object.
063     * Then use the XConnection object query() method to return a
064     * result set in the form of a row-set element.
065     * When you have finished working with the row-set, call the
066     * XConnection object close() method to terminate the connection.
067     */
068    public class XConnection
069    {
070    
071      /**
072       * Flag for DEBUG mode
073       */
074      private static final boolean DEBUG = false;
075    
076      /**
077       * The Current Connection Pool in Use. An XConnection can only
078       * represent one query at a time, prior to doing some type of query.
079       */
080      private ConnectionPool m_ConnectionPool = null;
081    
082      /**
083       * The DBMS Connection used to produce this SQL Document.
084       * Will be used to clear free up the database resources on
085       * close.
086       */
087      private Connection m_Connection = null;
088    
089      /**
090       * If a default Connection Pool is used. i.e. A connection Pool
091       * that is created internally, then do we actually allow pools
092       * to be created. Due to the archititure of the Xalan Extensions,
093       * there is no notification of when the Extension is being unloaded and
094       * as such, there is a good chance that JDBC COnnections are not closed.
095       * A finalized is provided to try and catch this situation but since
096       * support of finalizers is inconsistant across JVM's this may cause
097       * a problem. The robustness of the JDBC Driver is also at issue here.
098       * if a controlled shutdown is provided by the driver then default
099       * conntectiom pools are OK.
100       */
101      private boolean m_DefaultPoolingEnabled = false;
102    
103    
104      /**
105       * As we do queries, we will produce SQL Documents. Any ony may produce
106       * one or more SQL Documents so that the current connection information
107       * may be easilly reused. This collection will hold a collection of all
108       * the documents created. As Documents are closed, they will be removed
109       * from the collection and told to free all the used resources.
110       */
111      private Vector m_OpenSQLDocuments = new Vector();
112    
113    
114      /**
115       * Let's keep a copy of the ConnectionPoolMgr in
116       * alive here so we are keeping the static pool alive
117       * We will also use this Pool Manager to register our default pools.
118       */
119      private ConnectionPoolManager m_PoolMgr = new ConnectionPoolManager();
120    
121      /**
122       * For PreparedStatements, we need a place to
123       * to store the parameters in a vector.
124       */
125      private Vector m_ParameterList = new Vector();
126    
127      /**
128       * Allow the SQL Extensions to return null on error. The Error information will
129       * be stored in a seperate Error Document that can easily be retrived using the
130       * getError() method.
131       * %REVIEW% This functionality will probably be buried inside the SQLDocument.
132       */
133      private Exception m_Error = null;
134    
135      /**
136       * When the Stylesheet wants to review the errors from a paticular
137       * document, it asks the XConnection. We need to track what document
138       * in the list of managed documents caused the last error. As SetError
139       * is called, it will record the document that had the error.
140       */
141      private SQLDocument     m_LastSQLDocumentWithError = null;
142    
143      /**
144       * If true then full information should be returned about errors and warnings
145       * in getError(). This includes chained errors and warnings.
146       * If false (the default) then getError() returns just the first SQLException.
147       */
148      private boolean m_FullErrors = false;
149    
150    
151    
152      /**
153       * One a per XConnection basis there is a master QueryParser that is responsible
154       * for generating Query Parsers. This will allow us to cache previous instances
155       * so the inline parser execution time is minimized.
156       */
157      private SQLQueryParser m_QueryParser = new SQLQueryParser();
158    
159      /**
160       */
161      private boolean m_IsDefaultPool = false;
162    
163      /**
164       * This flag will be used to indicate to the SQLDocument to use
165       * Streaming mode. Streeaming Mode will reduce the memory footprint
166       * to a fixed amount but will not let you traverse the tree more than
167       * once since the Row data will be reused for every Row in the Query.
168       */
169      private boolean m_IsStreamingEnabled = true;
170    
171      /**
172       *
173       */
174       private boolean m_InlineVariables = false;
175    
176      /**
177       * This flag will be used to indicate if multiple result sets are
178       * supported from the database. If they are, then the metadata element is
179       * moved to insude the row-set element and multiple row-set elements may
180       * be included under the sql root element.
181       */
182      private boolean m_IsMultipleResultsEnabled = false;
183    
184      /**
185       * This flag will be used to indicate if database preparedstatements
186       * should be cached. This also controls if the Java statement object
187       * should be cached.
188       */
189      private boolean m_IsStatementCachingEnabled = false;
190    
191      /**
192       */
193      public XConnection( )
194      {
195      }
196    
197      /**
198       * Constructs a new XConnection and attempts to connect to a datasource as
199       * defined in the
200       * <code>connect(ExpressionContext exprContext, String connPoolName)</code>
201       * method.
202       * <code>org.apache.xalan.lib.sql.ConnectionPool</code> or a JNDI datasource.
203       *
204       * @param exprContext Context automatically passed from the XSLT sheet.
205       * @param name The name of the ConnectionPool or the JNDI DataSource path.
206       *
207       */
208      public XConnection( ExpressionContext exprContext, String connPoolName )
209      {
210        connect(exprContext, connPoolName);
211      }
212    
213      /**
214       * @param exprContext
215       * @param driver
216       * @param dbURL
217       */
218      public XConnection( ExpressionContext exprContext, String driver, String dbURL )
219      {
220        connect(exprContext, driver, dbURL);
221      }
222    
223      /**
224       * @param exprContext
225       * @param list
226       */
227      public XConnection( ExpressionContext exprContext, NodeList list )
228      {
229        connect(exprContext, list);
230      }
231    
232      /**
233       * @param exprContext
234       * @param driver
235       * @param dbURL
236       * @param user
237       * @param password
238       */
239      public XConnection( ExpressionContext exprContext, String driver, String dbURL, String user, String password )
240      {
241        connect(exprContext, driver, dbURL, user, password);
242      }
243    
244      /**
245       * @param exprContext
246       * @param driver
247       * @param dbURL
248       * @param protocolElem
249       */
250      public XConnection( ExpressionContext exprContext, String driver, String dbURL, Element protocolElem )
251      {
252        connect(exprContext, driver, dbURL, protocolElem);
253      }
254    
255      /**
256        * Returns an XConnection from either a user created
257        * <code>org.apache.xalan.lib.sql.ConnectionPool</code> or a JNDI datasource.
258    
259        * 
260        * This method first tries to resolve the passed name against
261        * <code>ConnectionPool</code>s registered with
262        * <code>ConnectionPoolManager</code>.
263        * If that fails, it attempts to find the name as a JNDI DataSource path.
264        *
265        * @param exprContext Context automatically passed from the XSLT sheet.
266        * @param name The name of the ConnectionPool or the JNDI DataSource path.
267        *
268        */
269       public XBooleanStatic connect( ExpressionContext exprContext, String name )
270       {
271         try
272         {
273           m_ConnectionPool = m_PoolMgr.getPool(name);
274    
275           if (m_ConnectionPool == null)
276           {
277             //Try to create a jndi source with the passed name
278             ConnectionPool pool = new JNDIConnectionPool(name);
279            
280             if (pool.testConnection())
281             {
282              
283               //JNDIConnectionPool seems good, so register it with the pool manager.
284               //Once registered, it will show up like other named ConnectionPool's,
285               //so the m_PoolMgr.getPool(name) method (above) will find it.
286               m_PoolMgr.registerPool(name, pool);
287               m_ConnectionPool = pool;
288              
289               m_IsDefaultPool = false;
290               return new XBooleanStatic(true);
291             }
292             else
293             {
294               throw new IllegalArgumentException(
295                   "Invalid ConnectionPool name or JNDI Datasource path: " + name);
296             }
297           }
298           else
299           {
300             m_IsDefaultPool = false;
301             return new XBooleanStatic(true);
302           }
303         }
304         catch (Exception e)
305         {
306           setError(e, exprContext);
307           return new XBooleanStatic(false);
308         }
309    
310       }
311    
312    
313      /**
314       * Create an XConnection object with just a driver and database URL.
315       * @param exprContext
316       * @param driver JDBC driver of the form foo.bar.Driver.
317       * @param dbURL database URL of the form jdbc:subprotocol:subname.
318       *
319       */
320      public XBooleanStatic connect( ExpressionContext exprContext, String driver, String dbURL )
321      {
322        try
323        {
324          init(driver, dbURL, new Properties());
325          return new XBooleanStatic(true);
326        }
327        catch(SQLException e)
328        {
329          setError(e,exprContext);
330          return new XBooleanStatic(false);
331        }
332        catch (Exception e)
333        {
334          setError(e,exprContext);
335          return new XBooleanStatic(false);
336        }
337      }
338    
339      /**
340       * @param exprContext
341       * @param protocolElem
342       *
343       */
344      public XBooleanStatic connect( ExpressionContext exprContext, Element protocolElem )
345      {
346        try
347        {
348          initFromElement(protocolElem);
349          return new XBooleanStatic(true);
350        }
351        catch(SQLException e)
352        {
353          setError(e,exprContext);
354          return new XBooleanStatic(false);
355        }
356        catch (Exception e)
357        {
358          setError(e,exprContext);
359          return new XBooleanStatic(false);
360        }
361      }
362    
363      /**
364       * @param exprContext
365       * @param list
366       *
367       */
368      public XBooleanStatic connect( ExpressionContext exprContext, NodeList list )
369      {
370        try
371        {
372          initFromElement( (Element) list.item(0) );
373          return new XBooleanStatic(true);
374        }
375        catch(SQLException e)
376        {
377          setError(e, exprContext);
378          return new XBooleanStatic(false);
379        }
380        catch (Exception e)
381        {
382          setError(e,exprContext);
383          return new XBooleanStatic(false);
384        }
385      }
386    
387      /**
388       * Create an XConnection object with user ID and password.
389       * @param exprContext
390       * @param driver JDBC driver of the form foo.bar.Driver.
391       * @param dbURL database URL of the form jdbc:subprotocol:subname.
392       * @param user user ID.
393       * @param password connection password.
394       *
395       */
396      public XBooleanStatic connect( ExpressionContext exprContext, String driver, String dbURL, String user, String password )
397      {
398        try
399        {
400          Properties prop = new Properties();
401          prop.put("user", user);
402          prop.put("password", password);
403    
404          init(driver, dbURL, prop);
405    
406          return new XBooleanStatic(true);
407        }
408        catch(SQLException e)
409        {
410          setError(e,exprContext);
411          return new XBooleanStatic(false);
412        }
413        catch (Exception e)
414        {
415          setError(e,exprContext);
416          return new XBooleanStatic(false);
417        }
418      }
419    
420    
421      /**
422       * Create an XConnection object with a connection protocol
423       * @param exprContext
424       * @param driver JDBC driver of the form foo.bar.Driver.
425       * @param dbURL database URL of the form jdbc:subprotocol:subname.
426       * @param protocolElem list of string tag/value connection arguments,
427       * normally including at least "user" and "password".
428       *
429       */
430      public XBooleanStatic connect( ExpressionContext exprContext, String driver, String dbURL, Element protocolElem )
431      {
432        try
433        {
434          Properties prop = new Properties();
435    
436          NamedNodeMap atts = protocolElem.getAttributes();
437    
438          for (int i = 0; i < atts.getLength(); i++)
439          {
440            prop.put(atts.item(i).getNodeName(), atts.item(i).getNodeValue());
441          }
442    
443          init(driver, dbURL, prop);
444    
445          return new XBooleanStatic(true);
446        }
447        catch(SQLException e)
448        {
449          setError(e,exprContext);
450          return new XBooleanStatic(false);
451        }
452        catch (Exception e)
453        {
454          setError(e, exprContext);
455          return new XBooleanStatic(false);
456        }
457      }
458    
459    
460      /**
461       * Allow the database connection information to be sepcified in
462       * the XML tree. The connection information could also be
463       * externally originated and passed in as an XSL Parameter.
464       * The required XML Format is as follows.
465       * A document fragment is needed to specify the connection information
466       * the top tag name is not specific for this code, we are only interested
467       * in the tags inside.
468       * <DBINFO-TAG>
469       * Specify the driver name for this connection pool
470       * <dbdriver>drivername</dbdriver>
471       * Specify the URL for the driver in this connection pool
472       * <dburl>url</dburl>
473       * Specify the password for this connection pool
474       * <password>password</password>
475       * Specify the username for this connection pool
476       * <user>username</user>
477       * You can add extra protocol items including the User Name & Password
478       * with the protocol tag. For each extra protocol item, add a new element
479       * where the name of the item is specified as the name attribute and
480       * and its value as the elements value.
481       * <protocol name="name of value">value</protocol>
482       * </DBINFO-TAG>
483       * @param e
484       *
485       * @throws SQLException
486       */
487      private void initFromElement( Element e )throws SQLException
488      {
489    
490        Properties prop = new Properties();
491        String driver = "";
492        String dbURL = "";
493        Node n = e.getFirstChild();
494    
495        if (null == n) return; // really need to throw an error
496    
497        do
498        {
499          String nName = n.getNodeName();
500    
501          if (nName.equalsIgnoreCase("dbdriver"))
502          {
503            driver = "";
504            Node n1 = n.getFirstChild();
505            if (null != n1)
506            {
507              driver = n1.getNodeValue();
508            }
509          }
510    
511          if (nName.equalsIgnoreCase("dburl"))
512          {
513            dbURL = "";
514            Node n1 = n.getFirstChild();
515            if (null != n1)
516            {
517              dbURL = n1.getNodeValue();
518            }
519          }
520    
521          if (nName.equalsIgnoreCase("password"))
522          {
523            String s = "";
524            Node n1 = n.getFirstChild();
525            if (null != n1)
526            {
527              s = n1.getNodeValue();
528            }
529            prop.put("password", s);
530          }
531    
532          if (nName.equalsIgnoreCase("user"))
533          {
534            String s = "";
535            Node n1 = n.getFirstChild();
536            if (null != n1)
537            {
538              s = n1.getNodeValue();
539            }
540            prop.put("user", s);
541          }
542    
543          if (nName.equalsIgnoreCase("protocol"))
544          {
545            String Name = "";
546    
547            NamedNodeMap attrs = n.getAttributes();
548            Node n1 = attrs.getNamedItem("name");
549            if (null != n1)
550            {
551              String s = "";
552              Name = n1.getNodeValue();
553    
554              Node n2 = n.getFirstChild();
555              if (null != n2) s = n2.getNodeValue();
556    
557              prop.put(Name, s);
558            }
559          }
560    
561        } while ( (n = n.getNextSibling()) != null);
562    
563        init(driver, dbURL, prop);
564      }
565    
566    
567    
568      /**
569       * Initilize is being called because we did not have an
570       * existing Connection Pool, so let's see if we created one
571       * already or lets create one ourselves.
572       * @param driver
573       * @param dbURL
574       * @param prop
575       *
576       * @throws SQLException
577       */
578      private void init( String driver, String dbURL, Properties prop )throws SQLException
579      {
580        Connection con = null;
581    
582        if (DEBUG)
583          System.out.println("XConnection, Connection Init");
584    
585        String user = prop.getProperty("user");
586        if (user == null) user = "";
587    
588        String passwd = prop.getProperty("password");
589        if (passwd == null) passwd = "";
590    
591    
592        String poolName = driver + dbURL + user + passwd;
593        ConnectionPool cpool = m_PoolMgr.getPool(poolName);
594    
595        if (cpool == null)
596        {
597    
598          if (DEBUG)
599          {
600            System.out.println("XConnection, Creating Connection");
601            System.out.println(" Driver  :" + driver);
602            System.out.println(" URL     :" + dbURL);
603            System.out.println(" user    :" + user);
604            System.out.println(" passwd  :" + passwd);
605          }
606    
607    
608          DefaultConnectionPool defpool = new DefaultConnectionPool();
609    
610          if ((DEBUG) && (defpool == null))
611            System.out.println("Failed to Create a Default Connection Pool");
612    
613          defpool.setDriver(driver);
614          defpool.setURL(dbURL);
615          defpool.setProtocol(prop);
616    
617          // Only enable pooling in the default pool if we are explicatly
618          // told too.
619          if (m_DefaultPoolingEnabled) defpool.setPoolEnabled(true);
620    
621          m_PoolMgr.registerPool(poolName, defpool);
622          m_ConnectionPool = defpool;
623        }
624        else
625        {
626          m_ConnectionPool = cpool;
627        }
628    
629        m_IsDefaultPool = true;
630    
631        //
632        // Let's test to see if we really can connect
633        // Just remember to give it back after the test.
634        //
635        try
636        {
637          con = m_ConnectionPool.getConnection();
638        }
639        catch(SQLException e)
640        {
641          if (con != null)
642          {
643            m_ConnectionPool.releaseConnectionOnError(con);
644            con = null;
645          }
646          throw e;
647        }
648        finally
649        {
650          if ( con != null ) m_ConnectionPool.releaseConnection(con);
651        }
652      }
653    
654      /**
655       * Allow the SQL Document to retrive a connection to be used
656       * to build the SQL Statement.
657       */
658      public ConnectionPool getConnectionPool()
659      {
660        return m_ConnectionPool;
661      }
662    
663    
664      /**
665       * Execute a query statement by instantiating an
666       * @param exprContext
667       * @param queryString the SQL query.
668       * @return XStatement implements NodeIterator.
669       * @throws SQLException
670       * @link org.apache.xalan.lib.sql.XStatement XStatement}
671       * object. The XStatement executes the query, and uses the result set
672       * to create a
673       * @link org.apache.xalan.lib.sql.RowSet RowSet},
674       * a row-set element.
675       */
676      public DTM query( ExpressionContext exprContext, String queryString )
677      {
678        SQLDocument doc = null;
679    
680        try
681        {
682          if (DEBUG) System.out.println("pquery()");
683    
684          // Build an Error Document, NOT Connected.
685          if ( null == m_ConnectionPool ) return null;
686    
687          SQLQueryParser query =
688              m_QueryParser.parse
689                (this, queryString, SQLQueryParser.NO_INLINE_PARSER);
690    
691          doc = SQLDocument.getNewDocument(exprContext);
692          doc.execute(this, query);
693    
694          // also keep a local reference
695          m_OpenSQLDocuments.addElement(doc);
696        }
697        catch (Exception e)
698        {
699          // OK We had an error building the document, let try and grab the
700          // error information and clean up our connections.
701    
702          if (DEBUG) System.out.println("exception in query()");
703    
704          if (doc != null)
705          {
706            if (doc.hasErrors())
707            {
708              setError(e, doc, doc.checkWarnings());
709            }
710    
711            doc.close(m_IsDefaultPool);
712            doc = null;
713          }
714        }
715        finally
716        {
717          if (DEBUG) System.out.println("leaving query()");
718        }
719    
720        // Doc will be null if there was an error
721        return doc;
722      }
723    
724      /**
725       * Execute a parameterized query statement by instantiating an
726       * @param exprContext
727       * @param queryString the SQL query.
728       * @return XStatement implements NodeIterator.
729       * @throws SQLException
730       * @link org.apache.xalan.lib.sql.XStatement XStatement}
731       * object. The XStatement executes the query, and uses the result set
732       * to create a
733       * @link org.apache.xalan.lib.sql.RowSet RowSet},
734       * a row-set element.
735       */
736      public DTM pquery( ExpressionContext exprContext, String queryString )
737      {
738        return(pquery(exprContext, queryString, null));
739      }
740    
741      /**
742       * Execute a parameterized query statement by instantiating an
743       * @param exprContext
744       * @param queryString the SQL query.
745       * @param typeInfo
746       * @return XStatement implements NodeIterator.
747       * @throws SQLException
748       * @link org.apache.xalan.lib.sql.XStatement XStatement}
749       * object. The XStatement executes the query, and uses the result set
750       * to create a
751       * @link org.apache.xalan.lib.sql.RowSet RowSet},
752       * a row-set element.
753       * This method allows for the user to pass in a comma seperated
754       * String that represents a list of parameter types. If supplied
755       * the parameter types will be used to overload the current types
756       * in the current parameter list.
757       */
758      public DTM pquery( ExpressionContext exprContext, String queryString, String typeInfo)
759      {
760        SQLDocument doc = null;
761    
762        try
763        {
764          if (DEBUG) System.out.println("pquery()");
765    
766          // Build an Error Document, NOT Connected.
767          if ( null == m_ConnectionPool ) return null;
768    
769          SQLQueryParser query =
770              m_QueryParser.parse
771                (this, queryString, SQLQueryParser.NO_OVERRIDE);
772    
773          // If we are not using the inline parser, then let add the data
774          // to the parser so it can populate the SQL Statement.
775          if ( !m_InlineVariables )
776          {
777            addTypeToData(typeInfo);
778            query.setParameters(m_ParameterList);
779          }
780    
781          doc = SQLDocument.getNewDocument(exprContext);
782          doc.execute(this, query);
783    
784          // also keep a local reference
785          m_OpenSQLDocuments.addElement(doc);
786        }
787        catch (Exception e)
788        {
789          // OK We had an error building the document, let try and grab the
790          // error information and clean up our connections.
791    
792          if (DEBUG) System.out.println("exception in query()");
793    
794          if (doc != null)
795          {
796            if (doc.hasErrors())
797            {
798              setError(e, doc, doc.checkWarnings());
799            }
800    
801            // If we are using the Default Connection Pool, then
802            // force the connection pool to flush unused connections.
803            doc.close(m_IsDefaultPool);
804            doc = null;
805          }
806        }
807        finally
808        {
809          if (DEBUG) System.out.println("leaving query()");
810        }
811    
812        // Doc will be null if there was an error
813        return doc;
814      }
815    
816      /**
817       * The purpose of this routine is to force the DB cursor to skip forward
818       * N records. You should call this function after [p]query to help with
819       * pagination. i.e. Perfrom your select, then skip forward past the records
820       * you read previously.
821       * 
822       * @param exprContext
823       * @param o
824       * @param value
825       */
826      public void skipRec( ExpressionContext exprContext, Object o, int value )
827      {
828        SQLDocument sqldoc = null;
829        DTMNodeIterator nodei = null;
830          
831        sqldoc = locateSQLDocument( exprContext, o);
832        if (sqldoc != null) sqldoc.skip(value);
833      }
834    
835      
836    
837      private void addTypeToData(String typeInfo)
838      {
839          int indx;
840    
841          if ( typeInfo != null && m_ParameterList != null )
842          {
843              // Parse up the parameter types that were defined
844              // with the query
845              StringTokenizer plist = new StringTokenizer(typeInfo);
846    
847              // Override the existing type that is stored in the
848              // parameter list. If there are more types than parameters
849              // ignore for now, a more meaningfull error should occur
850              // when the actual query is executed.
851              indx = 0;
852              while (plist.hasMoreTokens())
853              {
854                String value = plist.nextToken();
855                QueryParameter qp = (QueryParameter) m_ParameterList.elementAt(indx);
856                if ( null != qp )
857                {
858                  qp.setTypeName(value);
859                }
860    
861                indx++;
862              }
863          }
864      }
865    
866      /**
867       * Add an untyped value to the parameter list.
868       * @param value
869       *
870       */
871      public void addParameter( String value )
872      {
873        addParameterWithType(value, null);
874      }
875    
876      /**
877       * Add a typed parameter to the parameter list.
878       * @param value
879       * @param Type
880       *
881       */
882      public void addParameterWithType( String value, String Type )
883      {
884        m_ParameterList.addElement( new QueryParameter(value, Type) );
885      }
886    
887    
888      /**
889       * Add a single parameter to the parameter list
890       * formatted as an Element
891       * @param e
892       *
893       */
894      public void addParameterFromElement( Element e )
895      {
896        NamedNodeMap attrs = e.getAttributes();
897        Node Type = attrs.getNamedItem("type");
898        Node n1  = e.getFirstChild();
899        if (null != n1)
900        {
901          String value = n1.getNodeValue();
902          if (value == null) value = "";
903          m_ParameterList.addElement( new QueryParameter(value, Type.getNodeValue()) );
904        }
905      }
906    
907    
908      /**
909       * Add a section of parameters to the Parameter List
910       * Do each element from the list
911       * @param nl
912       *
913       */
914      public void addParameterFromElement( NodeList nl )
915      {
916        //
917        // Each child of the NodeList represents a node
918        // match from the select= statment. Process each
919        // of them as a seperate list.
920        // The XML Format is as follows
921        //
922        // <START-TAG>
923        //   <TAG1 type="int">value</TAG1>
924        //   <TAGA type="int">value</TAGA>
925        //   <TAG2 type="string">value</TAG2>
926        // </START-TAG>
927        //
928        // The XSL to process this is formatted as follows
929        // <xsl:param name="plist" select="//START-TAG" />
930        // <sql:addParameter( $plist );
931        //
932        int count = nl.getLength();
933        for (int x=0; x<count; x++)
934        {
935          addParameters( (Element) nl.item(x));
936        }
937      }
938    
939      /**
940       * @param elem
941       *
942       */
943      private void addParameters( Element elem )
944      {
945        //
946        // Process all of the Child Elements
947        // The format is as follows
948        //
949        //<TAG type ="typeid">value</TAG>
950        //<TAG1 type ="typeid">value</TAG1>
951        //<TAGA type ="typeid">value</TAGA>
952        //
953        // The name of the Node is not important just is value
954        // and if it contains a type attribute
955    
956        Node n = elem.getFirstChild();
957    
958        if (null == n) return;
959    
960        do
961        {
962          if (n.getNodeType() == Node.ELEMENT_NODE)
963          {
964            NamedNodeMap attrs = n.getAttributes();
965            Node Type = attrs.getNamedItem("type");
966            String TypeStr;
967    
968            if (Type == null) TypeStr = "string";
969            else TypeStr = Type.getNodeValue();
970    
971            Node n1  = n.getFirstChild();
972            if (null != n1)
973            {
974              String value = n1.getNodeValue();
975              if (value == null) value = "";
976    
977    
978              m_ParameterList.addElement(
979                new QueryParameter(value, TypeStr) );
980            }
981          }
982        } while ( (n = n.getNextSibling()) != null);
983      }
984    
985      /**
986       *
987       */
988      public void clearParameters( )
989      {
990        m_ParameterList.removeAllElements();
991      }
992    
993      /**
994       * There is a problem with some JDBC drivers when a Connection
995       * is open and the JVM shutsdown. If there is a problem, there
996       * is no way to control the currently open connections in the
997       * pool. So for the default connection pool, the actuall pooling
998       * mechinsm is disabled by default. The Stylesheet designer can
999       * re-enabled pooling to take advantage of connection pools.
1000       * The connection pool can even be disabled which will close all
1001       * outstanding connections.
1002       *
1003       * @deprecated Use setFeature("default-pool-enabled", "true");
1004       */
1005      public void enableDefaultConnectionPool( )
1006      {
1007    
1008        if (DEBUG)
1009          System.out.println("Enabling Default Connection Pool");
1010    
1011        m_DefaultPoolingEnabled = true;
1012    
1013        if (m_ConnectionPool == null) return;
1014        if (m_IsDefaultPool) return;
1015    
1016        m_ConnectionPool.setPoolEnabled(true);
1017    
1018      }
1019    
1020      /**
1021       * See enableDefaultConnectionPool
1022       *
1023       * @deprecated Use setFeature("default-pool-enabled", "false");
1024       */
1025      public void disableDefaultConnectionPool( )
1026      {
1027        if (DEBUG)
1028          System.out.println("Disabling Default Connection Pool");
1029    
1030        m_DefaultPoolingEnabled = false;
1031    
1032        if (m_ConnectionPool == null) return;
1033        if (!m_IsDefaultPool) return;
1034    
1035        m_ConnectionPool.setPoolEnabled(false);
1036      }
1037    
1038    
1039      /**
1040       * Control how the SQL Document uses memory. In Streaming Mode,
1041       * memory consumption is greatly reduces so you can have queries
1042       * of unlimited size but it will not let you traverse the data
1043       * more than once.
1044       *
1045       * @deprecated Use setFeature("streaming", "true");
1046       */
1047      public void enableStreamingMode( )
1048      {
1049    
1050        if (DEBUG)
1051          System.out.println("Enabling Streaming Mode");
1052    
1053        m_IsStreamingEnabled = true;
1054      }
1055    
1056      /**
1057       * Control how the SQL Document uses memory. In Streaming Mode,
1058       * memory consumption is greatly reduces so you can have queries
1059       * of unlimited size but it will not let you traverse the data
1060       * more than once.
1061       *
1062       * @deprecated Use setFeature("streaming", "false");
1063       */
1064      public void disableStreamingMode( )
1065      {
1066    
1067        if (DEBUG)
1068          System.out.println("Disable Streaming Mode");
1069    
1070        m_IsStreamingEnabled = false;
1071      }
1072    
1073      /**
1074       * Provide access to the last error that occued. This error
1075       * may be over written when the next operation occurs.
1076       *
1077       */
1078      public DTM getError( )
1079      {
1080        if ( m_FullErrors )
1081        {
1082          for ( int idx = 0 ; idx < m_OpenSQLDocuments.size() ; idx++ )
1083          {
1084            SQLDocument doc = (SQLDocument)m_OpenSQLDocuments.elementAt(idx);
1085            SQLWarning warn = doc.checkWarnings();
1086            if ( warn != null ) setError(null, doc, warn);
1087          }
1088        }
1089    
1090        return(buildErrorDocument());
1091      }
1092    
1093      /**
1094       * Close the connection to the data source.
1095       *
1096       * @throws SQLException
1097       */
1098      public void close( )throws SQLException
1099      {
1100        if (DEBUG)
1101          System.out.println("Entering XConnection.close()");
1102    
1103        //
1104        // This function is included just for Legacy support
1105        // If it is really called then we must me using a single
1106        // document interface, so close all open documents.
1107    
1108        while(m_OpenSQLDocuments.size() != 0)
1109        {
1110          SQLDocument d = (SQLDocument) m_OpenSQLDocuments.elementAt(0);
1111          try
1112          {
1113            // If we are using the Default Connection Pool, then
1114            // force the connection pool to flush unused connections.
1115            d.close(m_IsDefaultPool);
1116          }
1117          catch (Exception se ) {}
1118    
1119          m_OpenSQLDocuments.removeElementAt(0);
1120        }
1121    
1122        if ( null != m_Connection )
1123        {
1124          m_ConnectionPool.releaseConnection(m_Connection);
1125          m_Connection = null;
1126        }
1127    
1128        if (DEBUG)
1129          System.out.println("Exiting XConnection.close");
1130      }
1131    
1132      /**
1133       * Close the connection to the data source. Only close the connections
1134       * for a single document.
1135       *
1136       * @throws SQLException
1137       */
1138    
1139      public void close(ExpressionContext exprContext, Object doc) throws SQLException 
1140      {
1141        if (DEBUG)
1142            System.out.println("Entering XConnection.close(" + doc + ")");
1143    
1144        SQLDocument sqlDoc = locateSQLDocument(exprContext, doc);
1145        if (sqlDoc != null)
1146        {
1147          // If we are using the Default Connection Pool, then
1148          // force the connection pool to flush unused connections.
1149          sqlDoc.close(m_IsDefaultPool);
1150          m_OpenSQLDocuments.remove(sqlDoc);
1151        } 
1152      }
1153    
1154    
1155      /**
1156       * When an SQL Document is returned as a DTM object, the XSL variable is actually 
1157       * assigned as a DTMIterator. This is a helper function that will allow you to get
1158       * a reference to the original SQLDocument from the iterator.
1159       * 
1160       * Original code submitted by 
1161       *  Moraine Didier mailto://didier.moraine@winterthur.be
1162       * @param doc
1163       * @return
1164       */
1165      private SQLDocument locateSQLDocument(ExpressionContext exprContext, Object doc)
1166      {
1167        try
1168        {
1169          if (doc instanceof DTMNodeIterator)
1170          {
1171            DTMNodeIterator dtmIter = (DTMNodeIterator)doc;
1172            try
1173            {
1174              DTMNodeProxy root = (DTMNodeProxy)dtmIter.getRoot();
1175              return (SQLDocument) root.getDTM();
1176            }
1177            catch (Exception e)
1178            {
1179              XNodeSet xNS = (XNodeSet)dtmIter.getDTMIterator();
1180              DTMIterator iter = (DTMIterator)xNS.getContainedIter();
1181              DTM dtm = iter.getDTM(xNS.nextNode());
1182              return (SQLDocument)dtm;
1183            }
1184          }
1185    
1186    /*
1187          XNodeSet xNS = (XNodeSet)dtmIter.getDTMIterator();
1188          OneStepIterator iter = (OneStepIterator)xNS.getContainedIter();
1189          DTMManager aDTMManager = (DTMManager)iter.getDTMManager();
1190          return (SQLDocument)aDTMManager.getDTM(xNS.nextNode());
1191    */
1192          setError(new Exception("SQL Extension:close - Can Not Identify SQLDocument"), exprContext);    
1193          return null;  
1194        }
1195        catch(Exception e)
1196        {
1197          setError(e, exprContext);
1198          return null;
1199        }
1200      }
1201      
1202      /**
1203       * @param exprContext
1204       * @param excp
1205       *
1206       */
1207      private SQLErrorDocument buildErrorDocument()
1208      {
1209        SQLErrorDocument eDoc = null;
1210    
1211        if ( m_LastSQLDocumentWithError != null)
1212        {
1213          // @todo
1214          // Do we need to do something with this ??
1215          //    m_Error != null || (m_FullErrors && m_Warning != null) )
1216    
1217          ExpressionContext ctx = m_LastSQLDocumentWithError.getExpressionContext();
1218          SQLWarning        warn = m_LastSQLDocumentWithError.checkWarnings();
1219    
1220    
1221          try
1222          {
1223            DTMManager mgr =
1224              ((XPathContext.XPathExpressionContext)ctx).getDTMManager();
1225            DTMManagerDefault mgrDefault = (DTMManagerDefault) mgr;
1226            int dtmIdent = mgrDefault.getFirstFreeDTMID();
1227    
1228            eDoc = new SQLErrorDocument(
1229                mgr, dtmIdent<<DTMManager.IDENT_DTM_NODE_BITS,
1230                m_Error, warn, m_FullErrors);
1231    
1232            // Register our document
1233            mgrDefault.addDTM(eDoc, dtmIdent);
1234    
1235            // Clear the error and warning.
1236            m_Error = null;
1237            m_LastSQLDocumentWithError = null;
1238          }
1239          catch(Exception e)
1240          {
1241            eDoc = null;
1242          }
1243        }
1244    
1245        return(eDoc);
1246      }
1247    
1248    
1249      /**
1250       * This is an internal version of Set Error that is called withen
1251       * XConnection where there is no SQLDocument created yet. As in the
1252       * Connect statement or creation of the ConnectionPool.
1253       */
1254      public void setError(Exception excp,ExpressionContext expr)
1255      {
1256        try
1257        {
1258          ErrorListener listen = expr.getErrorListener();
1259          if ( listen != null && excp != null )
1260          {
1261            
1262            listen.warning(
1263              new TransformerException(excp.toString(),
1264              expr.getXPathContext().getSAXLocator(), excp));
1265          }
1266        }
1267        catch(Exception e) {}
1268      }
1269    
1270      /**
1271       * Set an error and/or warning on this connection.
1272       *
1273       */
1274      public void setError(Exception excp, SQLDocument doc, SQLWarning warn)
1275      {
1276    
1277        ExpressionContext cont = doc.getExpressionContext();
1278        m_LastSQLDocumentWithError = doc;
1279    
1280        try
1281        {
1282          ErrorListener listen = cont.getErrorListener();
1283          if ( listen != null && excp != null )
1284          listen.warning(
1285            new TransformerException(excp.toString(),
1286            cont.getXPathContext().getSAXLocator(), excp));
1287    
1288          if ( listen != null && warn != null )
1289          {
1290            listen.warning(new TransformerException(
1291              warn.toString(), cont.getXPathContext().getSAXLocator(), warn));
1292          }
1293    
1294          // Assume there will be just one error, but perhaps multiple warnings.
1295          if ( excp != null )  m_Error = excp;
1296    
1297          if ( warn != null )
1298          {
1299            // Because the log may not have processed the previous warning yet
1300            // we need to make a new one.
1301            SQLWarning tw =
1302              new SQLWarning(warn.getMessage(), warn.getSQLState(),
1303                warn.getErrorCode());
1304            SQLWarning nw = warn.getNextWarning();
1305            while ( nw != null )
1306            {
1307              tw.setNextWarning(new SQLWarning(nw.getMessage(),
1308                nw.getSQLState(), nw.getErrorCode()));
1309    
1310              nw = nw.getNextWarning();
1311            }
1312    
1313            tw.setNextWarning(
1314              new SQLWarning(warn.getMessage(), warn.getSQLState(),
1315                warn.getErrorCode()));
1316    
1317    //        m_Warning = tw;
1318    
1319          }
1320        }
1321        catch(Exception e)
1322        {
1323          //m_Error = null;
1324        }
1325      }
1326    
1327      /**
1328       * Set feature options for this XConnection.
1329       * @param feature The name of the feature being set, currently supports (streaming, inline-variables, multiple-results, cache-statements, default-pool-enabled).
1330       * @param setting The new setting for the specified feature, currently "true" is true and anything else is false.
1331       *
1332       */
1333      public void setFeature(String feature, String setting)
1334      {
1335        boolean value = false;
1336    
1337        if ( "true".equalsIgnoreCase(setting) ) value = true;
1338    
1339        if ( "streaming".equalsIgnoreCase(feature) )
1340        {
1341          m_IsStreamingEnabled = value;
1342        }
1343        else if ( "inline-variables".equalsIgnoreCase(feature) )
1344        {
1345          m_InlineVariables = value;
1346        }
1347        else if ( "multiple-results".equalsIgnoreCase(feature) )
1348        {
1349          m_IsMultipleResultsEnabled = value;
1350        }
1351        else if ( "cache-statements".equalsIgnoreCase(feature) )
1352        {
1353          m_IsStatementCachingEnabled = value;
1354        }
1355        else if ( "default-pool-enabled".equalsIgnoreCase(feature) )
1356        {
1357          m_DefaultPoolingEnabled = value;
1358    
1359          if (m_ConnectionPool == null) return;
1360          if (m_IsDefaultPool) return;
1361    
1362          m_ConnectionPool.setPoolEnabled(value);
1363        }
1364        else if ( "full-errors".equalsIgnoreCase(feature) )
1365        {
1366          m_FullErrors = value;
1367        }
1368      }
1369    
1370      /**
1371       * Get feature options for this XConnection.
1372       * @param feature The name of the feature to get the setting for.
1373       * @return The setting of the specified feature. Will be "true" or "false" (null if the feature is not known)
1374       */
1375      public String getFeature(String feature)
1376      {
1377        String value = null;
1378    
1379        if ( "streaming".equalsIgnoreCase(feature) )
1380          value = m_IsStreamingEnabled ? "true" : "false";
1381        else if ( "inline-variables".equalsIgnoreCase(feature) )
1382          value = m_InlineVariables ? "true" : "false";
1383        else if ( "multiple-results".equalsIgnoreCase(feature) )
1384          value = m_IsMultipleResultsEnabled ? "true" : "false";
1385        else if ( "cache-statements".equalsIgnoreCase(feature) )
1386          value = m_IsStatementCachingEnabled ? "true" : "false";
1387        else if ( "default-pool-enabled".equalsIgnoreCase(feature) )
1388          value = m_DefaultPoolingEnabled ? "true" : "false";
1389        else if ( "full-errors".equalsIgnoreCase(feature) )
1390          value = m_FullErrors ? "true" : "false";
1391    
1392        return(value);
1393      }
1394    
1395    
1396    
1397      /**
1398       *
1399       */
1400      protected void finalize( )
1401      {
1402        if (DEBUG) System.out.println("In XConnection, finalize");
1403        try
1404        {
1405          close();
1406        }
1407        catch(Exception e)
1408        {
1409          // Empty We are final Anyway
1410        }
1411      }
1412    
1413    }