View Javadoc

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      *   &lt;repository name=&quot;LocalUsers&quot;
275      *       class=&quot;org.apache.james.userrepository.JamesUsersJdbcRepository&quot;&gt;
276      *       &lt;!-- Name of the datasource to use --&gt;
277      *       &lt;data-source&gt;MailDb&lt;/data-source&gt;
278      *       &lt;!-- File to load the SQL definitions from --&gt;
279      *       &lt;sqlFile&gt;dist/conf/sqlResources.xml&lt;/sqlFile&gt;
280      *       &lt;!-- replacement parameters for the sql file --&gt;
281      *       &lt;sqlParameters table=&quot;JamesUsers&quot;/&gt;
282      *   &lt;/repository&gt;
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 }