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 }