1 /****************************************************************
2 * Licensed to the Apache Software Foundation (ASF) under one *
3 * or more contributor license agreements. See the NOTICE file *
4 * distributed with this work for additional information *
5 * regarding copyright ownership. The ASF licenses this file *
6 * to you under the Apache License, Version 2.0 (the *
7 * "License"); you may not use this file except in compliance *
8 * with the License. You may obtain a copy of the License at *
9 * *
10 * http://www.apache.org/licenses/LICENSE-2.0 *
11 * *
12 * Unless required by applicable law or agreed to in writing, *
13 * software distributed under the License is distributed on an *
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
15 * KIND, either express or implied. See the License for the *
16 * specific language governing permissions and limitations *
17 * under the License. *
18 ****************************************************************/
19
20 package org.apache.james.userrepository;
21
22 import org.apache.avalon.cornerstone.services.datasources.DataSourceSelector;
23 import org.apache.avalon.excalibur.datasource.DataSourceComponent;
24 import org.apache.avalon.framework.CascadingRuntimeException;
25 import org.apache.avalon.framework.activity.Initializable;
26 import org.apache.avalon.framework.configuration.Configuration;
27 import org.apache.avalon.framework.configuration.ConfigurationException;
28 import org.apache.avalon.framework.service.ServiceException;
29 import org.apache.avalon.framework.service.ServiceManager;
30 import org.apache.avalon.framework.service.Serviceable;
31 import org.apache.james.api.user.User;
32 import org.apache.james.impl.jamesuser.AbstractUsersRepository;
33 import org.apache.james.services.FileSystem;
34 import org.apache.james.util.sql.JDBCUtil;
35 import org.apache.james.util.sql.SqlResources;
36
37 import java.io.InputStream;
38 import java.sql.Connection;
39 import java.sql.DatabaseMetaData;
40 import java.sql.PreparedStatement;
41 import java.sql.ResultSet;
42 import java.sql.SQLException;
43 import java.util.ArrayList;
44 import java.util.Collection;
45 import java.util.HashMap;
46 import java.util.Iterator;
47 import java.util.List;
48 import java.util.Locale;
49 import java.util.Map;
50
51 /**
52 * An abstract base class for creating UserRepository implementations
53 * which use a database for persistence.
54 *
55 * To implement a new UserRepository using by extending this class,
56 * you need to implement the 3 abstract methods defined below,
57 * and define the required SQL statements in an SQLResources
58 * file.
59 *
60 * The SQL statements used by this implementation are:
61 * <TABLE>
62 * <TH><TD><B>Required</B></TD></TH>
63 * <TR><TD>select</TD><TD>Select all users.</TD></TR>
64 * <TR><TD>insert</TD><TD>Insert a user.</TD></TR>
65 * <TR><TD>update</TD><TD>Update a user.</TD></TR>
66 * <TR><TD>delete</TD><TD>Delete a user by name.</TD></TR>
67 * <TR><TD>createTable</TD><TD>Create the users table.</TD></TR>
68 * <TH><TD><B>Optional</B></TD></TH>
69 * <TR><TD>selectByLowercaseName</TD><TD>Select a user by name (case-insensitive lowercase).</TD></TR>
70 * </TABLE>
71 *
72 */
73 public abstract class AbstractJdbcUsersRepository extends
74 AbstractUsersRepository implements Serviceable, Initializable {
75
76
77 protected Map m_sqlParameters;
78
79 private String m_sqlFileName;
80
81 private String m_datasourceName;
82
83 private DataSourceSelector m_datasources;
84
85 private DataSourceComponent m_datasource;
86
87 // Fetches all Users from the db.
88 private String m_getUsersSql;
89
90 // This fetch a user by name, ensuring case-insensitive matching.
91 private String m_userByNameCaseInsensitiveSql;
92
93 // Insert, update and delete sql statements are not guaranteed
94 // to be case-insensitive; this is handled in code.
95 private String m_insertUserSql;
96
97 private String m_updateUserSql;
98
99 private String m_deleteUserSql;
100
101 // The JDBCUtil helper class
102 private JDBCUtil theJDBCUtil;
103
104 private FileSystem fileSystem;
105
106 /**
107 * Removes a user from the repository
108 *
109 * @param userName
110 * the user to be removed
111 */
112 public void removeUser(String userName) {
113 User user = getUserByName(userName);
114 if (user != null) {
115 doRemoveUser(user);
116 }
117 }
118
119 /**
120 * Get the user object with the specified user name. Return null if no such
121 * user.
122 *
123 * @param name
124 * the name of the user to retrieve
125 *
126 * @return the user if found, null otherwise
127 *
128 * @since James 1.2.2
129 */
130 public User getUserByName(String name) {
131 return getUserByName(name, ignoreCase);
132 }
133
134 /**
135 * Get the user object with the specified user name. Match user naems on a
136 * case insensitive basis. Return null if no such user.
137 *
138 * @param name
139 * the name of the user to retrieve
140 *
141 * @return the user if found, null otherwise
142 *
143 * @since James 1.2.2
144 */
145 public User getUserByNameCaseInsensitive(String name) {
146 return getUserByName(name, true);
147 }
148
149 /**
150 * Returns the user name of the user matching name on an equalsIgnoreCase
151 * basis. Returns null if no match.
152 *
153 * @param name
154 * the name of the user to retrieve
155 *
156 * @return the correct case sensitive name of the user
157 */
158 public String getRealName(String name) {
159 // Get the user by name, ignoring case, and return the correct name.
160 User user = getUserByName(name, ignoreCase);
161 if (user == null) {
162 return null;
163 } else {
164 return user.getUserName();
165 }
166 }
167
168 /**
169 * Returns whether or not this user is in the repository
170 *
171 * @return true or false
172 */
173 public boolean contains(String name) {
174 User user = getUserByName(name, ignoreCase);
175 return (user != null);
176 }
177
178 /**
179 * Returns whether or not this user is in the repository. Names are matched
180 * on a case insensitive basis.
181 *
182 * @return true or false
183 */
184 public boolean containsCaseInsensitive(String name) {
185 User user = getUserByName(name, true);
186 return (user != null);
187 }
188
189 /**
190 * Test if user with name 'name' has password 'password'.
191 *
192 * @param name
193 * the name of the user to be tested
194 * @param password
195 * the password to be tested
196 *
197 * @return true if the test is successful, false if the password is
198 * incorrect or the user doesn't exist
199 * @since James 1.2.2
200 */
201 public boolean test(String name, String password) {
202 User user = getUserByName(name, ignoreCase);
203 if (user == null) {
204 return false;
205 } else {
206 return user.verifyPassword(password);
207 }
208 }
209
210 /**
211 * Returns a count of the users in the repository.
212 *
213 * @return the number of users in the repository
214 */
215 public int countUsers() {
216 List usernames = listUserNames();
217 return usernames.size();
218 }
219
220 /**
221 * List users in repository.
222 *
223 * @return Iterator over a collection of Strings, each being one user in the
224 * repository.
225 */
226 public Iterator list() {
227 return listUserNames().iterator();
228 }
229
230 /**
231 * Set the DataSourceSelector
232 *
233 * @param datasources
234 * the DataSourceSelector
235 */
236 void setDatasources(DataSourceSelector datasources) {
237 m_datasources = datasources;
238 }
239
240 /**
241 * @see org.apache.avalon.framework.service.Serviceable#service(ServiceManager)
242 */
243 public void service(final ServiceManager componentManager)
244 throws ServiceException {
245 StringBuffer logBuffer = null;
246 if (getLogger().isDebugEnabled()) {
247 logBuffer = new StringBuffer(64).append(this.getClass().getName())
248 .append(".compose()");
249 getLogger().debug(logBuffer.toString());
250 }
251
252 setDatasources((DataSourceSelector) componentManager
253 .lookup(DataSourceSelector.ROLE));
254 setFileSystem((FileSystem) componentManager.lookup(FileSystem.ROLE));
255 }
256
257 /**
258 * Sets the filesystem service
259 *
260 * @param system
261 * the new service
262 */
263 void setFileSystem(FileSystem system) {
264 this.fileSystem = system;
265 }
266
267 /**
268 * Configures the UserRepository for JDBC access.<br>
269 * <br>
270 * Requires a configuration element in the .conf.xml file of the form:<br>
271 * <br>
272 *
273 * <pre>
274 * <repository name="LocalUsers"
275 * class="org.apache.james.userrepository.JamesUsersJdbcRepository">
276 * <!-- Name of the datasource to use -->
277 * <data-source>MailDb</data-source>
278 * <!-- File to load the SQL definitions from -->
279 * <sqlFile>dist/conf/sqlResources.xml</sqlFile>
280 * <!-- replacement parameters for the sql file -->
281 * <sqlParameters table="JamesUsers"/>
282 * </repository>
283 * </pre>
284 *
285 * @see org.apache.avalon.framework.configuration.Configurable#configure(Configuration)
286 */
287 public void configure(Configuration configuration)
288 throws ConfigurationException {
289 super.configure(configuration);
290 StringBuffer logBuffer = null;
291 if (getLogger().isDebugEnabled()) {
292 logBuffer = new StringBuffer(64).append(this.getClass().getName())
293 .append(".configure()");
294 getLogger().debug(logBuffer.toString());
295 }
296
297 // Parse the DestinationURL for the name of the datasource,
298 // the table to use, and the (optional) repository Key.
299 String destUrl = configuration.getAttribute("destinationURL");
300 // normalise the destination, to simplify processing.
301 if (!destUrl.endsWith("/")) {
302 destUrl += "/";
303 }
304 // Split on "/", starting after "db://"
305 List urlParams = new ArrayList();
306 int start = 5;
307 int end = destUrl.indexOf('/', start);
308 while (end > -1) {
309 urlParams.add(destUrl.substring(start, end));
310 start = end + 1;
311 end = destUrl.indexOf('/', start);
312 }
313
314 // Build SqlParameters and get datasource name from URL parameters
315 m_sqlParameters = new HashMap();
316 switch (urlParams.size()) {
317 case 3:
318 m_sqlParameters.put("key", urlParams.get(2));
319 case 2:
320 m_sqlParameters.put("table", urlParams.get(1));
321 case 1:
322 m_datasourceName = (String) urlParams.get(0);
323 break;
324 default:
325 throw new ConfigurationException(
326 "Malformed destinationURL - "
327 + "Must be of the format \"db://<data-source>[/<table>[/<key>]]\".");
328 }
329
330 if (getLogger().isDebugEnabled()) {
331 logBuffer = new StringBuffer(128).append("Parsed URL: table = '")
332 .append(m_sqlParameters.get("table")).append("', key = '")
333 .append(m_sqlParameters.get("key")).append("'");
334 getLogger().debug(logBuffer.toString());
335 }
336
337 // Get the SQL file location
338 m_sqlFileName = configuration.getChild("sqlFile", true).getValue();
339
340 // Get other sql parameters from the configuration object,
341 // if any.
342 Configuration sqlParamsConfig = configuration.getChild("sqlParameters");
343 String[] paramNames = sqlParamsConfig.getAttributeNames();
344 for (int i = 0; i < paramNames.length; i++) {
345 String paramName = paramNames[i];
346 String paramValue = sqlParamsConfig.getAttribute(paramName);
347 m_sqlParameters.put(paramName, paramValue);
348 }
349 }
350
351 /**
352 * <p>Initialises the JDBC repository.</p>
353 * <p>1) Tests the connection to the database.</p>
354 * <p>2) Loads SQL strings from the SQL definition file,
355 * choosing the appropriate SQL for this connection,
356 * and performing parameter substitution,</p>
357 * <p>3) Initialises the database with the required tables, if necessary.</p>
358 *
359 * @throws Exception if an error occurs
360 *
361 * @see org.apache.avalon.framework.activity.Initializable#initialize()
362 */
363 public void initialize() throws Exception {
364 StringBuffer logBuffer = null;
365 if (getLogger().isDebugEnabled()) {
366 logBuffer = new StringBuffer(128).append(this.getClass().getName())
367 .append(".initialize()");
368 getLogger().debug(logBuffer.toString());
369 }
370
371 theJDBCUtil = new JDBCUtil() {
372 protected void delegatedLog(String logString) {
373 AbstractJdbcUsersRepository.this.getLogger().warn(
374 "AbstractJdbcUsersRepository: " + logString);
375 }
376 };
377
378 // Get the data-source required.
379 m_datasource = (DataSourceComponent) m_datasources
380 .select(m_datasourceName);
381
382 // Test the connection to the database, by getting the DatabaseMetaData.
383 Connection conn = openConnection();
384 try {
385 DatabaseMetaData dbMetaData = conn.getMetaData();
386
387 InputStream sqlFile = null;
388
389 try {
390 sqlFile = fileSystem.getResource(m_sqlFileName);
391 } catch (Exception e) {
392 getLogger().fatalError(e.getMessage(), e);
393 throw e;
394 }
395
396 if (getLogger().isDebugEnabled()) {
397 logBuffer = new StringBuffer(256).append(
398 "Reading SQL resources from: ").append(
399 m_sqlFileName).append(", section ").append(
400 this.getClass().getName()).append(".");
401 getLogger().debug(logBuffer.toString());
402 }
403
404 SqlResources sqlStatements = new SqlResources();
405 sqlStatements.init(sqlFile, this.getClass().getName(), conn,
406 m_sqlParameters);
407
408 // Create the SQL Strings to use for this table.
409 // Fetches all Users from the db.
410 m_getUsersSql = sqlStatements.getSqlString("select", true);
411
412 // Get a user by lowercase name. (optional)
413 // If not provided, the entire list is iterated to find a user.
414 m_userByNameCaseInsensitiveSql = sqlStatements
415 .getSqlString("selectByLowercaseName");
416
417 // Insert, update and delete are not guaranteed to be
418 // case-insensitive
419 // Will always be called with correct case in username..
420 m_insertUserSql = sqlStatements.getSqlString("insert", true);
421 m_updateUserSql = sqlStatements.getSqlString("update", true);
422 m_deleteUserSql = sqlStatements.getSqlString("delete", true);
423
424 // Creates a single table with "username" the Primary Key.
425 String createUserTableSql = sqlStatements.getSqlString(
426 "createTable", true);
427
428 // Check if the required table exists. If not, create it.
429 // The table name is defined in the SqlResources.
430 String tableName = sqlStatements.getSqlString("tableName", true);
431
432 // Need to ask in the case that identifiers are stored, ask the
433 // DatabaseMetaInfo.
434 // NB this should work, but some drivers (eg mm MySQL)
435 // don't return the right details, hence the hackery below.
436 /*
437 * String tableName = m_tableName; if (
438 * dbMetaData.storesLowerCaseIdentifiers() ) { tableName =
439 * tableName.toLowerCase(Locale.US); } else if (
440 * dbMetaData.storesUpperCaseIdentifiers() ) { tableName =
441 * tableName.toUpperCase(Locale.US); }
442 */
443
444 // Try UPPER, lower, and MixedCase, to see if the table is there.
445 if (!theJDBCUtil.tableExists(dbMetaData, tableName)) {
446 // Users table doesn't exist - create it.
447 PreparedStatement createStatement = null;
448 try {
449 createStatement = conn.prepareStatement(createUserTableSql);
450 createStatement.execute();
451 } finally {
452 theJDBCUtil.closeJDBCStatement(createStatement);
453 }
454
455 logBuffer = new StringBuffer(128).append(
456 this.getClass().getName()).append(": Created table \'")
457 .append(tableName).append("\'.");
458 getLogger().info(logBuffer.toString());
459 } else {
460 if (getLogger().isDebugEnabled()) {
461 getLogger().debug("Using table: " + tableName);
462 }
463 }
464
465 } finally {
466 theJDBCUtil.closeJDBCConnection(conn);
467 }
468 }
469
470 /**
471 * Produces the complete list of User names, with correct case.
472 *
473 * @return a <code>List</code> of <code>String</code>s representing
474 * user names.
475 */
476 protected List listUserNames() {
477 Collection users = getAllUsers();
478 List userNames = new ArrayList(users.size());
479 for (Iterator it = users.iterator(); it.hasNext();) {
480 userNames.add(((User) it.next()).getUserName());
481 }
482 users.clear();
483 return userNames;
484 }
485
486 /**
487 * Returns a list populated with all of the Users in the repository.
488 *
489 * @return an <code>Iterator</code> of <code>User</code>s.
490 */
491 protected Iterator listAllUsers() {
492 return getAllUsers().iterator();
493 }
494
495 /**
496 * Returns a list populated with all of the Users in the repository.
497 *
498 * @return a <code>Collection</code> of <code>JamesUser</code>s.
499 */
500 private Collection getAllUsers() {
501 List userList = new ArrayList(); // Build the users into this list.
502
503 Connection conn = openConnection();
504 PreparedStatement getUsersStatement = null;
505 ResultSet rsUsers = null;
506 try {
507 // Get a ResultSet containing all users.
508 getUsersStatement = conn.prepareStatement(m_getUsersSql);
509 rsUsers = getUsersStatement.executeQuery();
510
511 // Loop through and build a User for every row.
512 while (rsUsers.next()) {
513 User user = readUserFromResultSet(rsUsers);
514 userList.add(user);
515 }
516 } catch (SQLException sqlExc) {
517 sqlExc.printStackTrace();
518 throw new CascadingRuntimeException("Error accessing database",
519 sqlExc);
520 } finally {
521 theJDBCUtil.closeJDBCResultSet(rsUsers);
522 theJDBCUtil.closeJDBCStatement(getUsersStatement);
523 theJDBCUtil.closeJDBCConnection(conn);
524 }
525
526 return userList;
527 }
528
529 /**
530 * Adds a user to the underlying Repository. The user name must not clash
531 * with an existing user.
532 *
533 * @param user
534 * the user to add
535 */
536 protected void doAddUser(User user) {
537 Connection conn = openConnection();
538 PreparedStatement addUserStatement = null;
539
540 // Insert into the database.
541 try {
542 // Get a PreparedStatement for the insert.
543 addUserStatement = conn.prepareStatement(m_insertUserSql);
544
545 setUserForInsertStatement(user, addUserStatement);
546
547 addUserStatement.execute();
548 } catch (SQLException sqlExc) {
549 sqlExc.printStackTrace();
550 throw new CascadingRuntimeException("Error accessing database",
551 sqlExc);
552 } finally {
553 theJDBCUtil.closeJDBCStatement(addUserStatement);
554 theJDBCUtil.closeJDBCConnection(conn);
555 }
556 }
557
558 /**
559 * Removes a user from the underlying repository. If the user doesn't exist,
560 * returns ok.
561 *
562 * @param user
563 * the user to remove
564 */
565 protected void doRemoveUser(User user) {
566 String username = user.getUserName();
567
568 Connection conn = openConnection();
569 PreparedStatement removeUserStatement = null;
570
571 // Delete from the database.
572 try {
573 removeUserStatement = conn.prepareStatement(m_deleteUserSql);
574 removeUserStatement.setString(1, username);
575 removeUserStatement.execute();
576 } catch (SQLException sqlExc) {
577 sqlExc.printStackTrace();
578 throw new CascadingRuntimeException("Error accessing database",
579 sqlExc);
580 } finally {
581 theJDBCUtil.closeJDBCStatement(removeUserStatement);
582 theJDBCUtil.closeJDBCConnection(conn);
583 }
584 }
585
586 /**
587 * Updates a user record to match the supplied User.
588 *
589 * @param user
590 * the user to update
591 */
592 protected void doUpdateUser(User user) {
593 Connection conn = openConnection();
594 PreparedStatement updateUserStatement = null;
595
596 // Update the database.
597 try {
598 updateUserStatement = conn.prepareStatement(m_updateUserSql);
599 setUserForUpdateStatement(user, updateUserStatement);
600 updateUserStatement.execute();
601 } catch (SQLException sqlExc) {
602 sqlExc.printStackTrace();
603 throw new CascadingRuntimeException("Error accessing database",
604 sqlExc);
605 } finally {
606 theJDBCUtil.closeJDBCStatement(updateUserStatement);
607 theJDBCUtil.closeJDBCConnection(conn);
608 }
609 }
610
611 /**
612 * Gets a user by name, ignoring case if specified. This implementation gets
613 * the entire set of users, and scrolls through searching for one matching
614 * <code>name</code>.
615 *
616 * @param name
617 * the name of the user being retrieved
618 * @param ignoreCase
619 * whether the name is regarded as case-insensitive
620 *
621 * @return the user being retrieved, null if the user doesn't exist
622 */
623 protected User getUserByNameIterating(String name, boolean ignoreCase) {
624 // Just iterate through all of the users until we find one matching.
625 Iterator users = listAllUsers();
626 while (users.hasNext()) {
627 User user = (User) users.next();
628 String username = user.getUserName();
629 if ((!ignoreCase && username.equals(name))
630 || (ignoreCase && username.equalsIgnoreCase(name))) {
631 return user;
632 }
633 }
634 // Not found - return null
635 return null;
636 }
637
638 /**
639 * Gets a user by name, ignoring case if specified. If the specified SQL
640 * statement has been defined, this method overrides the basic
641 * implementation in AbstractUsersRepository to increase performance.
642 *
643 * @param name
644 * the name of the user being retrieved
645 * @param ignoreCase
646 * whether the name is regarded as case-insensitive
647 *
648 * @return the user being retrieved, null if the user doesn't exist
649 */
650 protected User getUserByName(String name, boolean ignoreCase) {
651 // See if this statement has been set, if not, use
652 // simple superclass method.
653 if (m_userByNameCaseInsensitiveSql == null) {
654 return getUserByNameIterating(name, ignoreCase);
655 }
656
657 // Always get the user via case-insensitive SQL,
658 // then check case if necessary.
659 Connection conn = openConnection();
660 PreparedStatement getUsersStatement = null;
661 ResultSet rsUsers = null;
662 try {
663 // Get a ResultSet containing all users.
664 String sql = m_userByNameCaseInsensitiveSql;
665 getUsersStatement = conn.prepareStatement(sql);
666
667 getUsersStatement.setString(1, name.toLowerCase(Locale.US));
668
669 rsUsers = getUsersStatement.executeQuery();
670
671 // For case-insensitive matching, the first matching user will be
672 // returned.
673 User user = null;
674 while (rsUsers.next()) {
675 User rowUser = readUserFromResultSet(rsUsers);
676 String actualName = rowUser.getUserName();
677
678 // Check case before we assume it's the right one.
679 if (ignoreCase || actualName.equals(name)) {
680 user = rowUser;
681 break;
682 }
683 }
684 return user;
685 } catch (SQLException sqlExc) {
686 sqlExc.printStackTrace();
687 throw new CascadingRuntimeException("Error accessing database",
688 sqlExc);
689 } finally {
690 theJDBCUtil.closeJDBCResultSet(rsUsers);
691 theJDBCUtil.closeJDBCStatement(getUsersStatement);
692 theJDBCUtil.closeJDBCConnection(conn);
693 }
694 }
695
696 /**
697 * Reads properties for a User from an open ResultSet. Subclass
698 * implementations of this method must have knowledge of the fields
699 * presented by the "select" and "selectByLowercaseName" SQL statements.
700 * These implemenations may generate a subclass-specific User instance.
701 *
702 * @param rsUsers
703 * A ResultSet with a User record in the current row.
704 * @return A User instance
705 * @throws SQLException
706 * if an exception occurs reading from the ResultSet
707 */
708 protected abstract User readUserFromResultSet(ResultSet rsUsers)
709 throws SQLException;
710
711 /**
712 * Set parameters of a PreparedStatement object with property values from a
713 * User instance. Implementations of this method have knowledge of the
714 * parameter ordering of the "insert" SQL statement definition.
715 *
716 * @param user
717 * a User instance, which should be an implementation class which
718 * is handled by this Repostory implementation.
719 * @param userInsert
720 * a PreparedStatement initialised with SQL taken from the
721 * "insert" SQL definition.
722 * @throws SQLException
723 * if an exception occurs while setting parameter values.
724 */
725 protected abstract void setUserForInsertStatement(User user,
726 PreparedStatement userInsert) throws SQLException;
727
728 /**
729 * Set parameters of a PreparedStatement object with property values from a
730 * User instance. Implementations of this method have knowledge of the
731 * parameter ordering of the "update" SQL statement definition.
732 *
733 * @param user
734 * a User instance, which should be an implementation class which
735 * is handled by this Repostory implementation.
736 * @param userUpdate
737 * a PreparedStatement initialised with SQL taken from the
738 * "update" SQL definition.
739 * @throws SQLException
740 * if an exception occurs while setting parameter values.
741 */
742 protected abstract void setUserForUpdateStatement(User user,
743 PreparedStatement userUpdate) throws SQLException;
744
745 /**
746 * Opens a connection, throwing a runtime exception if a SQLException is
747 * encountered in the process.
748 *
749 * @return the new connection
750 */
751 private Connection openConnection() {
752 try {
753 return m_datasource.getConnection();
754 } catch (SQLException sqle) {
755 throw new CascadingRuntimeException(
756 "An exception occurred getting a database connection.",
757 sqle);
758 }
759 }
760 }