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            *
007     * "License"); you may not use this file except in compliance   *
008     * with the License.  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,   *
013     * software distributed under the License is distributed on an  *
014     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
015     * KIND, either express or implied.  See the License for the    *
016     * specific language governing permissions and limitations      *
017     * under the License.                                           *
018     ****************************************************************/
019    
020    package org.apache.james.user.jdbc;
021    
022    import org.apache.commons.configuration.ConfigurationException;
023    import org.apache.commons.configuration.HierarchicalConfiguration;
024    import org.apache.james.filesystem.api.FileSystem;
025    import org.apache.james.user.api.UsersRepositoryException;
026    import org.apache.james.user.api.model.User;
027    import org.apache.james.user.lib.AbstractJamesUsersRepository;
028    import org.apache.james.util.sql.JDBCUtil;
029    import org.apache.james.util.sql.SqlResources;
030    
031    import java.io.InputStream;
032    import java.sql.Connection;
033    import java.sql.DatabaseMetaData;
034    import java.sql.PreparedStatement;
035    import java.sql.ResultSet;
036    import java.sql.SQLException;
037    import java.util.ArrayList;
038    import java.util.Collection;
039    import java.util.HashMap;
040    import java.util.Iterator;
041    import java.util.List;
042    import java.util.Locale;
043    import java.util.Map;
044    
045    import javax.annotation.PostConstruct;
046    import javax.annotation.Resource;
047    import javax.sql.DataSource;
048    
049    /**
050     * An abstract base class for creating UserRepository implementations which use
051     * a database for persistence.
052     * 
053     * To implement a new UserRepository using by extending this class, you need to
054     * implement the 3 abstract methods defined below, and define the required SQL
055     * statements in an SQLResources file.
056     * 
057     * The SQL statements used by this implementation are:
058     * <table>
059     * <tr>
060     * <td><b>Required</b></td>
061     * <td></td>
062     * </tr>
063     * <tr>
064     * <td>select</td>
065     * <td>Select all users.</td>
066     * </tr>
067     * <tr>
068     * <td>insert</td>
069     * <td>Insert a user.</td>
070     * </tr>
071     * <tr>
072     * <td>update</td>
073     * <td>Update a user.</td>
074     * </tr>
075     * <tr>
076     * <td>delete</td>
077     * <td>Delete a user by name.</td>
078     * </tr>
079     * <tr>
080     * <td>createTable</td>
081     * <td>Create the users table.</td>
082     * </tr>
083     * <tr>
084     * <td><b>Optional</b></td>
085     * <td></td>
086     * </tr>
087     * <tr>
088     * <td>selectByLowercaseName</td>
089     * <td>Select a user by name (case-insensitive lowercase).</td>
090     * </tr>
091     * </table>
092     */
093    @Deprecated
094    public abstract class AbstractJdbcUsersRepository extends AbstractJamesUsersRepository {
095    
096        protected Map<String, String> m_sqlParameters;
097    
098        private String m_sqlFileName;
099    
100        private String m_datasourceName;
101    
102        private DataSource m_datasource;
103    
104        // Fetches all Users from the db.
105        private String m_getUsersSql;
106    
107        // This fetch a user by name, ensuring case-insensitive matching.
108        private String m_userByNameCaseInsensitiveSql;
109    
110        // Insert, update and delete sql statements are not guaranteed
111        // to be case-insensitive; this is handled in code.
112        private String m_insertUserSql;
113    
114        private String m_updateUserSql;
115    
116        private String m_deleteUserSql;
117    
118        // The JDBCUtil helper class
119        private JDBCUtil theJDBCUtil;
120    
121        private FileSystem fileSystem;
122    
123        /**
124         * Removes a user from the repository
125         * 
126         * @param userName
127         *            the user to be removed
128         * @throws UsersRepositoryException
129         */
130        public void removeUser(String userName) throws UsersRepositoryException {
131            User user = getUserByName(userName);
132            if (user != null) {
133                doRemoveUser(user);
134            } else {
135                throw new UsersRepositoryException("User " + userName + " does not exist");
136            }
137        }
138    
139        /**
140         * Get the user object with the specified user name. Return null if no such
141         * user.
142         * 
143         * @param name
144         *            the name of the user to retrieve
145         * 
146         * @return the user if found, null otherwise
147         * 
148         * @since James 1.2.2
149         */
150        public User getUserByName(String name) throws UsersRepositoryException {
151            return getUserByName(name, ignoreCase);
152        }
153    
154        /**
155         * Returns whether or not this user is in the repository
156         * 
157         * @return true or false
158         */
159        public boolean contains(String name) throws UsersRepositoryException {
160            User user = getUserByName(name, ignoreCase);
161            return (user != null);
162        }
163    
164        /**
165         * Returns whether or not this user is in the repository. Names are matched
166         * on a case insensitive basis.
167         * 
168         * @return true or false
169         */
170        public boolean containsCaseInsensitive(String name) throws UsersRepositoryException {
171            User user = getUserByName(name, true);
172            return (user != null);
173        }
174    
175        /**
176         * Test if user with name 'name' has password 'password'.
177         * 
178         * @param name
179         *            the name of the user to be tested
180         * @param password
181         *            the password to be tested
182         * 
183         * @return true if the test is successful, false if the password is
184         *         incorrect or the user doesn't exist
185         * @since James 1.2.2
186         */
187        public boolean test(String name, String password) throws UsersRepositoryException {
188            User user = getUserByName(name, ignoreCase);
189            if (user == null) {
190                return false;
191            } else {
192                return user.verifyPassword(password);
193            }
194        }
195    
196        /**
197         * Returns a count of the users in the repository.
198         * 
199         * @return the number of users in the repository
200         */
201        public int countUsers() throws UsersRepositoryException {
202            List<String> usernames = listUserNames();
203            return usernames.size();
204        }
205    
206        /**
207         * List users in repository.
208         * 
209         * @return Iterator over a collection of Strings, each being one user in the
210         *         repository.
211         */
212        public Iterator<String> list() throws UsersRepositoryException {
213            return listUserNames().iterator();
214        }
215    
216        /**
217         * Set the DataSourceSelector
218         * 
219         * @param m_datasource
220         *            the DataSourceSelector
221         */
222        @Resource(name = "datasource")
223        public void setDatasource(DataSource m_datasource) {
224            this.m_datasource = m_datasource;
225        }
226    
227        /**
228         * Sets the filesystem service
229         * 
230         * @param system
231         *            the new service
232         */
233        @Resource(name = "filesystem")
234        public void setFileSystem(FileSystem system) {
235            this.fileSystem = system;
236        }
237    
238        /**
239         * Initialises the JDBC repository.
240         * <ol>
241         * <li>Tests the connection to the database.</li>
242         * <li>Loads SQL strings from the SQL definition file, choosing the
243         * appropriate SQL for this connection, and performing parameter
244         * substitution,</li>
245         * <li>Initialises the database with the required tables, if necessary.</li>
246         * </ol>
247         * 
248         * @throws Exception
249         *             if an error occurs
250         */
251        @PostConstruct
252        public void init() throws Exception {
253            StringBuffer logBuffer = null;
254            if (getLogger().isDebugEnabled()) {
255                logBuffer = new StringBuffer(128).append(this.getClass().getName()).append(".initialize()");
256                getLogger().debug(logBuffer.toString());
257            }
258    
259            theJDBCUtil = new JDBCUtil() {
260                protected void delegatedLog(String logString) {
261                    AbstractJdbcUsersRepository.this.getLogger().warn("AbstractJdbcUsersRepository: " + logString);
262                }
263            };
264    
265            // Test the connection to the database, by getting the DatabaseMetaData.
266            Connection conn = openConnection();
267            try {
268                DatabaseMetaData dbMetaData = conn.getMetaData();
269    
270                InputStream sqlFile = null;
271    
272                try {
273                    sqlFile = fileSystem.getResource(m_sqlFileName);
274                } catch (Exception e) {
275                    getLogger().error(e.getMessage(), e);
276                    throw e;
277                }
278    
279                if (getLogger().isDebugEnabled()) {
280                    logBuffer = new StringBuffer(256).append("Reading SQL resources from: ").append(m_sqlFileName).append(", section ").append(this.getClass().getName()).append(".");
281                    getLogger().debug(logBuffer.toString());
282                }
283    
284                SqlResources sqlStatements = new SqlResources();
285                sqlStatements.init(sqlFile, this.getClass().getName(), conn, m_sqlParameters);
286    
287                // Create the SQL Strings to use for this table.
288                // Fetches all Users from the db.
289                m_getUsersSql = sqlStatements.getSqlString("select", true);
290    
291                // Get a user by lowercase name. (optional)
292                // If not provided, the entire list is iterated to find a user.
293                m_userByNameCaseInsensitiveSql = sqlStatements.getSqlString("selectByLowercaseName");
294    
295                // Insert, update and delete are not guaranteed to be
296                // case-insensitive
297                // Will always be called with correct case in username..
298                m_insertUserSql = sqlStatements.getSqlString("insert", true);
299                m_updateUserSql = sqlStatements.getSqlString("update", true);
300                m_deleteUserSql = sqlStatements.getSqlString("delete", true);
301    
302                // Creates a single table with "username" the Primary Key.
303                String createUserTableSql = sqlStatements.getSqlString("createTable", true);
304    
305                // Check if the required table exists. If not, create it.
306                // The table name is defined in the SqlResources.
307                String tableName = sqlStatements.getSqlString("tableName", true);
308    
309                // Need to ask in the case that identifiers are stored, ask the
310                // DatabaseMetaInfo.
311                // NB this should work, but some drivers (eg mm MySQL)
312                // don't return the right details, hence the hackery below.
313                /*
314                 * String tableName = m_tableName; if (
315                 * dbMetaData.storesLowerCaseIdentifiers() ) { tableName =
316                 * tableName.toLowerCase(Locale.US); } else if (
317                 * dbMetaData.storesUpperCaseIdentifiers() ) { tableName =
318                 * tableName.toUpperCase(Locale.US); }
319                 */
320    
321                // Try UPPER, lower, and MixedCase, to see if the table is there.
322                if (!theJDBCUtil.tableExists(dbMetaData, tableName)) {
323                    // Users table doesn't exist - create it.
324                    PreparedStatement createStatement = null;
325                    try {
326                        createStatement = conn.prepareStatement(createUserTableSql);
327                        createStatement.execute();
328                    } finally {
329                        theJDBCUtil.closeJDBCStatement(createStatement);
330                    }
331    
332                    logBuffer = new StringBuffer(128).append(this.getClass().getName()).append(": Created table \'").append(tableName).append("\'.");
333                    getLogger().info(logBuffer.toString());
334                } else {
335                    if (getLogger().isDebugEnabled()) {
336                        getLogger().debug("Using table: " + tableName);
337                    }
338                }
339    
340            } finally {
341                theJDBCUtil.closeJDBCConnection(conn);
342            }
343        }
344    
345        /**
346         * <p>
347         * Configures the UserRepository for JDBC access.
348         * </p>
349         * <p>
350         * Requires a configuration element in the .conf.xml file of the form:
351         * </p>
352         * 
353         * <pre>
354         *   &lt;repository name=&quot;so even &quot;
355         *       class=&quot;org.apache.james.userrepository.JamesUsersJdbcRepository&quot;&gt;
356         *       &lt;!-- Name of the datasource to use --&gt;
357         *       &lt;data-source&gt;MailDb&lt;/data-source&gt;
358         *       &lt;!-- File to load the SQL definitions from --&gt;
359         *       &lt;sqlFile&gt;dist/conf/sqlResources.xml&lt;/sqlFile&gt;
360         *       &lt;!-- replacement parameters for the sql file --&gt;
361         *       &lt;sqlParameters table=&quot;JamesUsers&quot;/&gt;
362         *   &lt;/repository&gt;
363         * </pre>
364         * 
365         * @see org.apache.james.user.lib.AbstractJamesUsersRepository#doConfigure(org.apache.commons.configuration.HierarchicalConfiguration)
366         */
367        @SuppressWarnings("unchecked")
368        protected void doConfigure(HierarchicalConfiguration configuration) throws ConfigurationException {
369            StringBuffer logBuffer = null;
370            if (getLogger().isDebugEnabled()) {
371                logBuffer = new StringBuffer(64).append(this.getClass().getName()).append(".configure()");
372                getLogger().debug(logBuffer.toString());
373            }
374    
375            // Parse the DestinationURL for the name of the datasource,
376            // the table to use, and the (optional) repository Key.
377            String destUrl = configuration.getString("[@destinationURL]", null);
378            // throw an exception if the attribute is missing
379            if (destUrl == null)
380                throw new ConfigurationException("destinationURL attribute is missing from Configuration");
381    
382            // normalise the destination, to simplify processing.
383            if (!destUrl.endsWith("/")) {
384                destUrl += "/";
385            }
386            // Split on "/", starting after "db://"
387            List<String> urlParams = new ArrayList<String>();
388            int start = 5;
389            int end = destUrl.indexOf('/', start);
390            while (end > -1) {
391                urlParams.add(destUrl.substring(start, end));
392                start = end + 1;
393                end = destUrl.indexOf('/', start);
394            }
395    
396            // Build SqlParameters and get datasource name from URL parameters
397            m_sqlParameters = new HashMap<String, String>();
398            switch (urlParams.size()) {
399            case 3:
400                m_sqlParameters.put("key", urlParams.get(2));
401            case 2:
402                m_sqlParameters.put("table", urlParams.get(1));
403            case 1:
404                m_datasourceName = (String) urlParams.get(0);
405                break;
406            default:
407                throw new ConfigurationException("Malformed destinationURL - " + "Must be of the format \"db://<data-source>[/<table>[/<key>]]\".");
408            }
409    
410            if (getLogger().isDebugEnabled()) {
411                logBuffer = new StringBuffer(128).append("Parsed URL: table = '").append(m_sqlParameters.get("table")).append("', key = '").append(m_sqlParameters.get("key")).append("'");
412                getLogger().debug(logBuffer.toString());
413            }
414    
415            // Get the SQL file location
416            m_sqlFileName = configuration.getString("sqlFile", null);
417    
418            // Get other sql parameters from the configuration object,
419            // if any.
420            Iterator<String> paramIt = configuration.getKeys("sqlParameters");
421            while (paramIt.hasNext()) {
422                String rawName = paramIt.next();
423                String paramName = paramIt.next().substring("sqlParameters.[@".length(), rawName.length() - 1);
424                String paramValue = configuration.getString(rawName);
425                m_sqlParameters.put(paramName, paramValue);
426            }
427        }
428    
429        /**
430         * Produces the complete list of User names, with correct case.
431         * 
432         * @return a <code>List</code> of <code>String</code>s representing user
433         *         names.
434         */
435        protected List<String> listUserNames() throws UsersRepositoryException {
436            Collection<User> users = getAllUsers();
437            List<String> userNames = new ArrayList<String>(users.size());
438            for (Iterator<User> it = users.iterator(); it.hasNext();) {
439                userNames.add(it.next().getUserName());
440            }
441            users.clear();
442            return userNames;
443        }
444    
445        /**
446         * Returns a list populated with all of the Users in the repository.
447         * 
448         * @return an <code>Iterator</code> of <code>User</code>s.
449         */
450        protected Iterator<User> listAllUsers() throws UsersRepositoryException {
451            return getAllUsers().iterator();
452        }
453    
454        /**
455         * Returns a list populated with all of the Users in the repository.
456         * 
457         * @return a <code>Collection</code> of <code>JamesUser</code>s.
458         * @throws UsersRepositoryException
459         */
460        private Collection<User> getAllUsers() throws UsersRepositoryException {
461            List<User> userList = new ArrayList<User>(); // Build the users into
462                                                         // this list.
463    
464            Connection conn = null;
465            PreparedStatement getUsersStatement = null;
466            ResultSet rsUsers = null;
467            try {
468                conn = openConnection();
469                // Get a ResultSet containing all users.
470                getUsersStatement = conn.prepareStatement(m_getUsersSql);
471                rsUsers = getUsersStatement.executeQuery();
472    
473                // Loop through and build a User for every row.
474                while (rsUsers.next()) {
475                    User user = readUserFromResultSet(rsUsers);
476                    userList.add(user);
477                }
478            } catch (SQLException sqlExc) {
479                sqlExc.printStackTrace();
480                throw new UsersRepositoryException("Error accessing database", sqlExc);
481            } finally {
482                theJDBCUtil.closeJDBCResultSet(rsUsers);
483                theJDBCUtil.closeJDBCStatement(getUsersStatement);
484                theJDBCUtil.closeJDBCConnection(conn);
485            }
486    
487            return userList;
488        }
489    
490        /**
491         * Adds a user to the underlying Repository. The user name must not clash
492         * with an existing user.
493         * 
494         * @param user
495         *            the user to add
496         * @throws UsersRepositoryException
497         */
498        protected void doAddUser(User user) throws UsersRepositoryException {
499            Connection conn = null;
500            PreparedStatement addUserStatement = null;
501    
502            // Insert into the database.
503            try {
504                conn = openConnection();
505                // Get a PreparedStatement for the insert.
506                addUserStatement = conn.prepareStatement(m_insertUserSql);
507    
508                setUserForInsertStatement(user, addUserStatement);
509    
510                addUserStatement.execute();
511            } catch (SQLException sqlExc) {
512                sqlExc.printStackTrace();
513                throw new UsersRepositoryException("Error accessing database", sqlExc);
514            } finally {
515                theJDBCUtil.closeJDBCStatement(addUserStatement);
516                theJDBCUtil.closeJDBCConnection(conn);
517            }
518        }
519    
520        /**
521         * Removes a user from the underlying repository. If the user doesn't exist,
522         * returns ok.
523         * 
524         * @param user
525         *            the user to remove
526         * @throws UsersRepositoryException
527         */
528        protected void doRemoveUser(User user) throws UsersRepositoryException {
529            String username = user.getUserName();
530    
531            Connection conn = null;
532            PreparedStatement removeUserStatement = null;
533    
534            // Delete from the database.
535            try {
536                conn = openConnection();
537                removeUserStatement = conn.prepareStatement(m_deleteUserSql);
538                removeUserStatement.setString(1, username);
539                removeUserStatement.execute();
540            } catch (SQLException sqlExc) {
541                sqlExc.printStackTrace();
542                throw new UsersRepositoryException("Error accessing database", sqlExc);
543            } finally {
544                theJDBCUtil.closeJDBCStatement(removeUserStatement);
545                theJDBCUtil.closeJDBCConnection(conn);
546            }
547        }
548    
549        /**
550         * Updates a user record to match the supplied User.
551         * 
552         * @param user
553         *            the user to update
554         * @throws UsersRepositoryException
555         */
556        protected void doUpdateUser(User user) throws UsersRepositoryException {
557            Connection conn = null;
558            PreparedStatement updateUserStatement = null;
559    
560            // Update the database.
561            try {
562                conn = openConnection();
563                updateUserStatement = conn.prepareStatement(m_updateUserSql);
564                setUserForUpdateStatement(user, updateUserStatement);
565                updateUserStatement.execute();
566            } catch (SQLException sqlExc) {
567                sqlExc.printStackTrace();
568                throw new UsersRepositoryException("Error accessing database", sqlExc);
569            } finally {
570                theJDBCUtil.closeJDBCStatement(updateUserStatement);
571                theJDBCUtil.closeJDBCConnection(conn);
572            }
573        }
574    
575        /**
576         * Gets a user by name, ignoring case if specified. This implementation gets
577         * the entire set of users, and scrolls through searching for one matching
578         * <code>name</code>.
579         * 
580         * @param name
581         *            the name of the user being retrieved
582         * @param ignoreCase
583         *            whether the name is regarded as case-insensitive
584         * 
585         * @return the user being retrieved, null if the user doesn't exist
586         * @throws UsersRepositoryException
587         */
588        protected User getUserByNameIterating(String name, boolean ignoreCase) throws UsersRepositoryException {
589            // Just iterate through all of the users until we find one matching.
590            Iterator<User> users = listAllUsers();
591            while (users.hasNext()) {
592                User user = users.next();
593                String username = user.getUserName();
594                if ((!ignoreCase && username.equals(name)) || (ignoreCase && username.equalsIgnoreCase(name))) {
595                    return user;
596                }
597            }
598            // Not found - return null
599            return null;
600        }
601    
602        /**
603         * Gets a user by name, ignoring case if specified. If the specified SQL
604         * statement has been defined, this method overrides the basic
605         * implementation in AbstractJamesUsersRepository to increase performance.
606         * 
607         * @param name
608         *            the name of the user being retrieved
609         * @param ignoreCase
610         *            whether the name is regarded as case-insensitive
611         * 
612         * @return the user being retrieved, null if the user doesn't exist
613         * @throws UsersRepositoryException
614         */
615        protected User getUserByName(String name, boolean ignoreCase) throws UsersRepositoryException {
616            // See if this statement has been set, if not, use
617            // simple superclass method.
618            if (m_userByNameCaseInsensitiveSql == null) {
619                return getUserByNameIterating(name, ignoreCase);
620            }
621    
622            // Always get the user via case-insensitive SQL,
623            // then check case if necessary.
624            Connection conn = null;
625            PreparedStatement getUsersStatement = null;
626            ResultSet rsUsers = null;
627            try {
628                conn = openConnection();
629                // Get a ResultSet containing all users.
630                String sql = m_userByNameCaseInsensitiveSql;
631                getUsersStatement = conn.prepareStatement(sql);
632    
633                getUsersStatement.setString(1, name.toLowerCase(Locale.US));
634    
635                rsUsers = getUsersStatement.executeQuery();
636    
637                // For case-insensitive matching, the first matching user will be
638                // returned.
639                User user = null;
640                while (rsUsers.next()) {
641                    User rowUser = readUserFromResultSet(rsUsers);
642                    String actualName = rowUser.getUserName();
643    
644                    // Check case before we assume it's the right one.
645                    if (ignoreCase || actualName.equals(name)) {
646                        user = rowUser;
647                        break;
648                    }
649                }
650                return user;
651            } catch (SQLException sqlExc) {
652                sqlExc.printStackTrace();
653                throw new UsersRepositoryException("Error accessing database", sqlExc);
654            } finally {
655                theJDBCUtil.closeJDBCResultSet(rsUsers);
656                theJDBCUtil.closeJDBCStatement(getUsersStatement);
657                theJDBCUtil.closeJDBCConnection(conn);
658            }
659        }
660    
661        /**
662         * Reads properties for a User from an open ResultSet. Subclass
663         * implementations of this method must have knowledge of the fields
664         * presented by the "select" and "selectByLowercaseName" SQL statements.
665         * These implemenations may generate a subclass-specific User instance.
666         * 
667         * @param rsUsers
668         *            A ResultSet with a User record in the current row.
669         * @return A User instance
670         * @throws SQLException
671         *             if an exception occurs reading from the ResultSet
672         */
673        protected abstract User readUserFromResultSet(ResultSet rsUsers) throws SQLException;
674    
675        /**
676         * Set parameters of a PreparedStatement object with property values from a
677         * User instance. Implementations of this method have knowledge of the
678         * parameter ordering of the "insert" SQL statement definition.
679         * 
680         * @param user
681         *            a User instance, which should be an implementation class which
682         *            is handled by this Repostory implementation.
683         * @param userInsert
684         *            a PreparedStatement initialised with SQL taken from the
685         *            "insert" SQL definition.
686         * @throws SQLException
687         *             if an exception occurs while setting parameter values.
688         */
689        protected abstract void setUserForInsertStatement(User user, PreparedStatement userInsert) throws SQLException;
690    
691        /**
692         * Set parameters of a PreparedStatement object with property values from a
693         * User instance. Implementations of this method have knowledge of the
694         * parameter ordering of the "update" SQL statement definition.
695         * 
696         * @param user
697         *            a User instance, which should be an implementation class which
698         *            is handled by this Repostory implementation.
699         * @param userUpdate
700         *            a PreparedStatement initialised with SQL taken from the
701         *            "update" SQL definition.
702         * @throws SQLException
703         *             if an exception occurs while setting parameter values.
704         */
705        protected abstract void setUserForUpdateStatement(User user, PreparedStatement userUpdate) throws SQLException;
706    
707        /**
708         * Opens a connection, throwing a runtime exception if a SQLException is
709         * encountered in the process.
710         * 
711         * @return the new connection
712         * @throws SQLException
713         */
714        private Connection openConnection() throws SQLException {
715            return m_datasource.getConnection();
716    
717        }
718    }