Source code: com/tripi/asp/ADODB/Connection.java
1 /**
2 * ArrowHead ASP Server
3 * This is a source file for the ArrowHead ASP Server - an 100% Java
4 * VBScript interpreter and ASP server.
5 *
6 * For more information, see http://www.tripi.com/arrowhead
7 *
8 * Copyright (C) 2002 Terence Haddock
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23 *
24 */
25 package com.tripi.asp.ADODB;
26
27 import java.sql.SQLException;
28 import java.util.Vector;
29 import java.util.Enumeration;
30 import jregex.*;
31
32 import com.tripi.asp.*;
33
34 import org.apache.log4j.Category;
35
36 /**
37 * This class represents the ADODB.Connection object. This object handles
38 * connections to the database.
39 * Implementation status:
40 * <ul>
41 * <li>Attributes - Implemented.
42 * <li>CommandTimeout - Implemented, but ignored.
43 * <li>ConnectionString - Implemented.
44 * <li>ConnectionTimeout - Implemented, but ignored.
45 * <li>CursorLocation - Implemented, but ignored.
46 * <li>DefaultDatabase - Implemented, but ignored, does not make sense in JDBC
47 * <li>Errors - Implemented
48 * <li>IsolationLevel - <b>TODO</b> Not implemented
49 * <li>Mode - <b>TODO</b> Not implemented
50 * <li>Properties - <b>TODO</b> Not implemented.
51 * <li>Provider - <b>TODO</b> Not implemented
52 * <li>State - Implemented
53 * <li>Version - <b>TODO</b> Not implemented
54 * <li>BeginTrans - Implemented
55 * <li>CommitTrans - Implemented
56 * <li>RollbackTrans - Implemented
57 * <li>Close - Implemented
58 * <li>Execute - Implemented
59 * <li>Open - Implemented
60 * <li>OpenSchema - <b>TODO</b> Not implemented
61 * </ul>
62 * @author Terence Haddock
63 */
64 public class Connection
65 {
66 /** Debugging category. */
67 Category DBG = Category.getInstance(Connection.class);
68
69 /** Current java.sql.Connection object. */
70 java.sql.Connection cx = null;
71
72 /** Are we in a transaction? */
73 protected boolean inTransaction = false;
74
75 /**
76 * Constructor, no arguments.
77 */
78 public Connection()
79 {
80 }
81
82 /**
83 * What to do on page end.
84 */
85 public void OnPageEnd(Object ignoreme)
86 {
87 if (DBG.isDebugEnabled()) DBG.debug("OnPageEnd called");
88 try {
89 /* Close the connection */
90 Close();
91 } catch (AspException ex)
92 {
93 DBG.error("Error on ADODB.Connection.OnPageEnd", ex);
94 }
95 }
96
97 /** List of errors thrown by this class. */
98 public ErrorsClass Errors = new ErrorsClass();
99
100 /** Attributes of this connection */
101 public AspCollection Attributes = new AspCollection();
102
103 /** Timeout for commands */
104 public int CommandTimeout;
105
106 /** Current connection string */
107 public String ConnectionString = null;
108
109 /** Timeout for connections */
110 public int ConnectionTimeout;
111
112 /** Cursor location, server or client. */
113 public int CursorLocation = 2;
114
115 /** Default database. */
116 public int DefaultDatabase;
117
118 /** Isolation level. */
119 public int IsolationLevel;
120
121 /** Mode for modifying data. */
122 public int Mode;
123
124 /** Properties list. */
125 public AspCollection Properties = new AspCollection();
126
127 /** Provider information */
128 public String Provider;
129
130 /**
131 * State, open or closed.
132 * @return state of connection
133 */
134 public int State()
135 {
136 if (cx == null)
137 return 0;
138 else
139 return 1;
140 }
141
142 /**
143 * Obtains the ASP Version.
144 * @return ASP Version, currently 2.5
145 */
146 public String Version()
147 {
148 return "2.5";
149 }
150
151 /**
152 * This sub-class is used to return multiple arguments from the
153 * parseConnectionInfo(String) function.
154 */
155 static class ConnectionInfo
156 {
157 /** The JDBC driver */
158 String driver = null;
159 /** The JDBC URL */
160 String url = null;
161 /** The username */
162 String username = null;
163 /** The password */
164 String password = null;
165 }
166
167 /**
168 * This function parses the given URL into parts. It supports the
169 * follow formats:
170 * <ul>
171 * <li>jdbc:<URL> - JDBC URLs
172 * <li>driver=<driver>;URL=<url>;UID=<user>;PWD=<password>
173 * <ul>
174 * <li>driver - JDBC driver class, such as org.postgresql.Driver
175 * <li>URL - JDBC URL, such as jdbc:postgres://database
176 * <li>UID - Username (optional)
177 * <li>PWD - Password (optional)
178 * </ul>
179 * </ul>
180 * @param url URL to parse
181 * @return Connection info
182 */
183 private ConnectionInfo parseConnectionInfo(String url)
184 {
185 ConnectionInfo cinfo = new ConnectionInfo();
186 if (url.startsWith("jdbc:"))
187 {
188 cinfo.url = url;
189 return cinfo;
190 }
191 final Pattern dpat = new Pattern("(^|;)(driver)=([^;]+)(;|$)",
192 REFlags.IGNORE_CASE);
193 Matcher dmat = dpat.matcher(url);
194 if (dmat.find()) cinfo.driver = dmat.group(3);
195 final Pattern rpat = new Pattern("(^|;)(url)=([^;]+)(;|$)",
196 REFlags.IGNORE_CASE);
197 Matcher rmat = rpat.matcher(url);
198 if (rmat.find()) cinfo.url = rmat.group(3);
199 final Pattern upat = new Pattern("(^|;)(user|uid|user id)=([^;]+)(;|$)",
200 REFlags.IGNORE_CASE);
201 Matcher umat = upat.matcher(url);
202 if (umat.find()) cinfo.username = umat.group(3);
203 final Pattern ppat = new Pattern("(^|;)(password|pwd)=([^;]+)(;|$)",
204 REFlags.IGNORE_CASE);
205 Matcher pmat = ppat.matcher(url);
206 if (pmat.find()) cinfo.password = pmat.group(3);
207
208 if (DBG.isDebugEnabled())
209 {
210 DBG.debug("Driver: " + cinfo.driver);
211 DBG.debug("URL: " + cinfo.url);
212 DBG.debug("User: " + cinfo.username);
213 DBG.debug("Password: " + cinfo.password);
214 }
215 return cinfo;
216 }
217
218 /**
219 * Open a URL with username and password.
220 * @param url URL to open
221 * @param username Username to use
222 * @param password Password to use
223 * @throws AspException on error
224 */
225 public void Open(String url, String username, String password)
226 throws AspException
227 {
228 this.ConnectionString = url;
229 ConnectionInfo cinfo = parseConnectionInfo(url);
230 try {
231 if (cinfo.driver != null)
232 try {
233 Class.forName(cinfo.driver).newInstance();
234 } catch (Exception ex) {
235 throw new AspNestedException(ex);
236 }
237 if (username != null) cinfo.username = username;
238 if (password != null) cinfo.password = password;
239 /* Close any open connections */
240 if (cx != null) Close();
241 if (DBG.isDebugEnabled())
242 {
243 DBG.debug("cinfo.url = " + cinfo.url);
244 DBG.debug("cinfo.username = " + cinfo.username);
245 DBG.debug("cinfo.password = " + cinfo.password);
246 }
247 if (cinfo.username == null && cinfo.password == null)
248 {
249 cx = java.sql.DriverManager.getConnection(cinfo.url);
250 } else {
251 cx = java.sql.DriverManager.getConnection(cinfo.url,
252 cinfo.username, cinfo.password);
253 }
254 } catch (SQLException ex) {
255 throw processException(ex);
256 }
257 }
258
259 /**
260 * Open a database connection, giving only an URL.
261 * @param url URL of database to open
262 * @throws AspException on error
263 */
264 public void Open(String url) throws AspException
265 {
266 Open(url, null, null);
267 }
268
269 /**
270 * Open a database using the ConnectionString parameter as the URL.
271 * @throws AspException on error
272 */
273 public void Open() throws AspException
274 {
275 Open(ConnectionString, null, null);
276 }
277
278 /**
279 * Begin a transaction. Returns the transaction level for nested
280 * transactions (not implemented).
281 * @return transaction level (always 1 for this implementation)
282 * @throws AspException on error, throws exception if called while
283 * already in a transaction.
284 */
285 public int BeginTrans() throws AspException
286 {
287 try {
288 if (inTransaction)
289 {
290 throw new AspNotImplementedException("Nested transactions not supported");
291 }
292 cx.setAutoCommit(false);
293 inTransaction = true;
294 } catch (SQLException ex) {
295 throw processException(ex);
296 }
297 return 1;
298 }
299
300 /**
301 * Commits the current, active transaction, throws an exception if called
302 * outside of a transaction.
303 * @throws AspException on error
304 */
305 public void CommitTrans() throws AspException
306 {
307 try {
308 if (!inTransaction)
309 {
310 throw new AspNotImplementedException("Commit called outside of transaction.");
311 }
312 cx.commit();
313 } catch (SQLException ex) {
314 throw processException(ex);
315 } finally {
316 try {
317 cx.setAutoCommit(true);
318 } catch (SQLException ex)
319 {
320 throw processException(ex);
321 }
322 inTransaction = false;
323 }
324 }
325
326 /**
327 * Rolls back the current, active transaction, throws an exception if called
328 * outside of a transaction.
329 * @throws AspException on error
330 */
331 public void RollbackTrans() throws AspException
332 {
333 try {
334 if (!inTransaction)
335 {
336 throw new AspNotImplementedException("Commit called outside of transaction.");
337 }
338 cx.rollback();
339 } catch (SQLException ex) {
340 throw processException(ex);
341 } finally {
342 try {
343 cx.setAutoCommit(false);
344 } catch (SQLException ex) {
345 throw processException(ex);
346 }
347
348 inTransaction = false;
349 }
350 }
351
352 /**
353 * Close the current, open database connection.
354 * @throws AspException on SQL exception
355 */
356 public void Close() throws AspException
357 {
358 if (cx == null) return;
359 try {
360 cx.close();
361 } catch (SQLException ex) {
362 throw processException(ex);
363 } finally {
364 cx = null;
365 }
366 }
367
368 /**
369 * Execute the given SQL statement.
370 * @param sql SQL expression to execute
371 * @return Record of results
372 * @throws AspException on SQL exception
373 */
374 public RecordSet Execute(String sql) throws AspException
375 {
376 return Execute(sql, null);
377 }
378
379 /**
380 * Executes the given SQL statement, returning a result value.
381 * @param sql SQL expression to execute
382 * @param recordsEffected by-reference variable which will contain the
383 * number of records modified.
384 * @return Record of results
385 * @throws AspException on SQL exception
386 */
387 public RecordSet Execute(String sql, ByRefValue recordsAffected)
388 throws AspException
389 {
390 return Execute(sql, recordsAffected, 0);
391 }
392
393 /**
394 * Executes the given SQL statement, returning a result value.
395 * @param sql SQL expression to execute
396 * @param recordsEffected by-reference variable which will contain the
397 * number of records modified.
398 * @param flags ADODB flags
399 * @return Record of results
400 * @throws AspException on SQL exception
401 */
402 public RecordSet Execute(String sql, ByRefValue recordsAffected, int flags)
403 throws AspException
404 {
405 RecordSet rc = new RecordSet();
406 Command cmd = new Command();
407 cmd.CommandText = sql;
408 cmd.ActiveConnection(this);
409
410 int res = rc.Open(cmd, null, 0, 0);
411 if ((recordsAffected != null) && (res != -1))
412 {
413 Integer ra = new Integer(res);
414 recordsAffected.setValue(ra);
415 }
416
417 return rc;
418 }
419
420 /**
421 * Process this exception, add it to the error list and re-throw it
422 * as an AspException. Package scope on purpose.
423 * @param ex Exception to process
424 */
425 AspException processException(Throwable ex)
426 {
427 return processException(ex, 0x80004005);
428 }
429
430 /**
431 * Process this exception, add it to the error list and re-throw it
432 * as an AspException. Package scope on purpose.
433 * @param ex Exception to process
434 * @param errorCode error code
435 */
436 AspException processException(Throwable ex, int errorCode)
437 {
438 AspException aspEx;
439 if (ex instanceof AspException)
440 {
441 aspEx = (AspException)ex;
442 } else {
443 aspEx = new AspNestedException(ex, errorCode);
444 }
445 Errors.AddError(aspEx);
446 return aspEx;
447 }
448
449 /**
450 * This inner class contains the list of SQL errors.
451 */
452 static public class ErrorsClass implements SimpleMap
453 {
454 /** Debugging context */
455 static Category DBG = Category.getInstance(ErrorsClass.class);
456
457 /** Internal list of SQL errors. */
458 protected Vector _errorlist = new Vector();
459
460 /**
461 * Returns number of errors in list.
462 * @return number of SQL errors
463 */
464 public int Count()
465 {
466 return _errorlist.size();
467 }
468
469 /**
470 * Clears the SQL errors
471 */
472 public void Clear()
473 {
474 _errorlist = new Vector();
475 }
476
477 /**
478 * Internal function to add an error to the list
479 * @param ex Exception to add to list
480 */
481 protected void AddError(AspException ex)
482 {
483 if (DBG.isDebugEnabled())
484 DBG.debug("AddError: " + ex);
485 _errorlist.add(ex);
486 }
487
488 /**
489 * Obtain the error at the specified index.
490 * @param obj Object to be coerced into integer
491 * @return Error at the specified index.
492 * @throws AspException on error
493 */
494 public Object get(Object obj) throws AspException
495 {
496 int i = Types.coerceToInteger(obj).intValue();
497
498 return _errorlist.get(i);
499 }
500
501 /**
502 * The Errors list is read-only, so this function will throw
503 * an error.
504 * @param obj Index
505 * @param value Value
506 * @throws AspException always throws read only exception
507 */
508 public void put(Object obj, Object value) throws AspException
509 {
510 throw new AspReadOnlyException("Errors");
511 }
512
513 /**
514 * Function to obtain keys in this collection. This function should
515 * not be called.
516 * @return enumeration of keys
517 */
518 public Enumeration getKeys()
519 {
520 return null;
521 }
522 }
523 }
524