FileDocCategorySizeDatePackage
AbstractJdbcUsersRepository.javaAPI DocApache James 2.3.123954Fri Jan 12 12:56:32 GMT 2007org.apache.james.userrepository

AbstractJdbcUsersRepository

public abstract class AbstractJdbcUsersRepository extends AbstractUsersRepository implements org.apache.avalon.framework.service.Serviceable, org.apache.avalon.framework.activity.Initializable, org.apache.avalon.framework.context.Contextualizable, org.apache.avalon.framework.configuration.Configurable
An abstract base class for creating UserRepository implementations which use a database for persistence. To implement a new UserRepository using by extending this class, you need to implement the 3 abstract methods defined below, and define the required SQL statements in an SQLResources file. The SQL statements used by this implementation are:
Required
selectSelect all users.
insertInsert a user.
updateUpdate a user.
deleteDelete a user by name.
createTableCreate the users table.
Optional
selectByLowercaseNameSelect a user by name (case-insensitive lowercase).

Fields Summary
protected org.apache.avalon.framework.context.Context
context
The Avalon context used by the instance
protected Map
m_sqlParameters
private String
m_sqlFileName
private String
m_datasourceName
private org.apache.avalon.cornerstone.services.datasources.DataSourceSelector
m_datasources
private org.apache.avalon.excalibur.datasource.DataSourceComponent
m_datasource
private String
m_getUsersSql
private String
m_userByNameCaseInsensitiveSql
private String
m_insertUserSql
private String
m_updateUserSql
private String
m_deleteUserSql
private String
m_createUserTableSql
private org.apache.james.util.JDBCUtil
theJDBCUtil
Constructors Summary
Methods Summary
public voidconfigure(org.apache.avalon.framework.configuration.Configuration configuration)
Configures the UserRepository for JDBC access.

Requires a configuration element in the .conf.xml file of the form:

<repository name="LocalUsers"
class="org.apache.james.userrepository.JamesUsersJdbcRepository">
<!-- Name of the datasource to use -->
<data-source>MailDb</data-source>
<!-- File to load the SQL definitions from -->
<sqlFile>dist/conf/sqlResources.xml</sqlFile>
<!-- replacement parameters for the sql file -->
<sqlParameters table="JamesUsers"/>
</repository>

        StringBuffer logBuffer = null;
        if (getLogger().isDebugEnabled()) {
            logBuffer =
                new StringBuffer(64)
                        .append(this.getClass().getName())
                        .append(".configure()");
            getLogger().debug( logBuffer.toString() );
        }

        // Parse the DestinationURL for the name of the datasource,
        // the table to use, and the (optional) repository Key.
        String destUrl = configuration.getAttribute("destinationURL");
        // normalise the destination, to simplify processing.
        if ( ! destUrl.endsWith("/") ) {
            destUrl += "/";
        }
        // Split on "/", starting after "db://"
        List urlParams = new ArrayList();
        int start = 5;
        int end = destUrl.indexOf('/", start);
        while ( end > -1 ) {
            urlParams.add(destUrl.substring(start, end));
            start = end + 1;
            end = destUrl.indexOf('/", start);
        }

        // Build SqlParameters and get datasource name from URL parameters
        m_sqlParameters = new HashMap();
        switch ( urlParams.size() ) {
        case 3:
            m_sqlParameters.put("key", urlParams.get(2));
        case 2:
            m_sqlParameters.put("table", urlParams.get(1));
        case 1:
            m_datasourceName = (String)urlParams.get(0);
            break;
        default:
            throw new ConfigurationException
                ("Malformed destinationURL - " +
                 "Must be of the format \"db://<data-source>[/<table>[/<key>]]\".");
        }

        if (getLogger().isDebugEnabled()) {
            logBuffer =
                new StringBuffer(128)
                        .append("Parsed URL: table = '")
                        .append(m_sqlParameters.get("table"))
                        .append("', key = '")
                        .append(m_sqlParameters.get("key"))
                        .append("'");
            getLogger().debug(logBuffer.toString());
        }

        // Get the SQL file location
        m_sqlFileName = configuration.getChild("sqlFile", true).getValue();
        if (!m_sqlFileName.startsWith("file://")) {
            throw new ConfigurationException
                ("Malformed sqlFile - Must be of the format \"file://<filename>\".");
        }

        // Get other sql parameters from the configuration object,
        // if any.
        Configuration sqlParamsConfig = configuration.getChild("sqlParameters");
        String[] paramNames = sqlParamsConfig.getAttributeNames();
        for (int i = 0; i < paramNames.length; i++ ) {
            String paramName = paramNames[i];
            String paramValue = sqlParamsConfig.getAttribute(paramName);
            m_sqlParameters.put(paramName, paramValue);
        }
    
public voidcontextualize(org.apache.avalon.framework.context.Context context)

see
org.apache.avalon.framework.context.Contextualizable#contextualize(Context)

        this.context = context;
    
protected voiddoAddUser(org.apache.james.services.User user)
Adds a user to the underlying Repository. The user name must not clash with an existing user.

param
user the user to be added

        Connection conn = openConnection();
        PreparedStatement addUserStatement = null;

        // Insert into the database.
        try {
            // Get a PreparedStatement for the insert.
            addUserStatement =
                conn.prepareStatement(m_insertUserSql);

            setUserForInsertStatement(user, addUserStatement);

            addUserStatement.execute();
        }
        catch ( SQLException sqlExc) {
            sqlExc.printStackTrace();
            throw new CascadingRuntimeException("Error accessing database", sqlExc);
        } finally {
            theJDBCUtil.closeJDBCStatement(addUserStatement);
            theJDBCUtil.closeJDBCConnection(conn);
        }
    
protected voiddoRemoveUser(org.apache.james.services.User user)
Removes a user from the underlying repository. If the user doesn't exist this method doesn't throw an exception.

param
user the user to be removed

        String username = user.getUserName();

        Connection conn = openConnection();
        PreparedStatement removeUserStatement = null;

        // Delete from the database.
        try {
            removeUserStatement = conn.prepareStatement(m_deleteUserSql);
            removeUserStatement.setString(1, username);
            removeUserStatement.execute();
        }
        catch ( SQLException sqlExc ) {
            sqlExc.printStackTrace();
            throw new CascadingRuntimeException("Error accessing database", sqlExc);
        } finally {
            theJDBCUtil.closeJDBCStatement(removeUserStatement);
            theJDBCUtil.closeJDBCConnection(conn);
        }
    
protected voiddoUpdateUser(org.apache.james.services.User user)
Updates a user record to match the supplied User.

param
user the updated user record

        Connection conn = openConnection();
        PreparedStatement updateUserStatement = null;

        // Update the database.
        try {
            updateUserStatement = conn.prepareStatement(m_updateUserSql);
            setUserForUpdateStatement(user, updateUserStatement);
            updateUserStatement.execute();
        }
        catch ( SQLException sqlExc ) {
            sqlExc.printStackTrace();
            throw new CascadingRuntimeException("Error accessing database", sqlExc);
        } finally {
            theJDBCUtil.closeJDBCStatement(updateUserStatement);
            theJDBCUtil.closeJDBCConnection(conn);
        }
    
private java.util.CollectiongetAllUsers()
Returns a list populated with all of the Users in the repository.

return
a Collection of JamesUsers.

        List userList = new ArrayList(); // Build the users into this list.

        Connection conn = openConnection();
        PreparedStatement getUsersStatement = null;
        ResultSet rsUsers = null;
        try {
            // Get a ResultSet containing all users.
            getUsersStatement = 
                               conn.prepareStatement(m_getUsersSql);
            rsUsers = getUsersStatement.executeQuery();

            // Loop through and build a User for every row.
            while ( rsUsers.next() ) {
                User user = readUserFromResultSet(rsUsers);
                userList.add(user);
            }
        }
        catch ( SQLException sqlExc) {
            sqlExc.printStackTrace();
            throw new CascadingRuntimeException("Error accessing database", sqlExc);
        }
        finally {
            theJDBCUtil.closeJDBCResultSet(rsUsers);
            theJDBCUtil.closeJDBCStatement(getUsersStatement);
            theJDBCUtil.closeJDBCConnection(conn);
        }

        return userList;
    
protected org.apache.james.services.UsergetUserByName(java.lang.String name, boolean ignoreCase)
Gets a user by name, ignoring case if specified. If the specified SQL statement has been defined, this method overrides the basic implementation in AbstractUsersRepository to increase performance.

param
name the name of the user being retrieved
param
ignoreCase whether the name is regarded as case-insensitive
return
the user being retrieved, null if the user doesn't exist

        // See if this statement has been set, if not, use
        // simple superclass method.
        if ( m_userByNameCaseInsensitiveSql == null ) {
            return super.getUserByName(name, ignoreCase);
        }

        // Always get the user via case-insensitive SQL,
        // then check case if necessary.
        Connection conn = openConnection();
        PreparedStatement getUsersStatement = null;
        ResultSet rsUsers = null;
        try {
            // Get a ResultSet containing all users.
            String sql = m_userByNameCaseInsensitiveSql;
            getUsersStatement = conn.prepareStatement(sql);

            getUsersStatement.setString(1, name.toLowerCase(Locale.US));

            rsUsers = getUsersStatement.executeQuery();

            // For case-insensitive matching, the first matching user will be returned.
            User user = null;
            while ( rsUsers.next() ) {
                User rowUser = readUserFromResultSet(rsUsers);
                String actualName = rowUser.getUserName();

                // Check case before we assume it's the right one.
                if ( ignoreCase || actualName.equals(name) ) {
                    user = rowUser;
                    break;
                }
            }
            return user;
        }
        catch ( SQLException sqlExc ) {
            sqlExc.printStackTrace();
            throw new CascadingRuntimeException("Error accessing database", sqlExc);
        }
        finally {
            theJDBCUtil.closeJDBCResultSet(rsUsers);
            theJDBCUtil.closeJDBCStatement(getUsersStatement);
            theJDBCUtil.closeJDBCConnection(conn);
        }
    
public voidinitialize()

Initialises the JDBC repository.

1) Tests the connection to the database.

2) Loads SQL strings from the SQL definition file, choosing the appropriate SQL for this connection, and performing parameter substitution,

3) Initialises the database with the required tables, if necessary.

throws
Exception if an error occurs

        StringBuffer logBuffer = null;
        if (getLogger().isDebugEnabled()) {
            logBuffer =
                new StringBuffer(128)
                        .append(this.getClass().getName())
                        .append(".initialize()");
            getLogger().debug( logBuffer.toString() );
        }

        theJDBCUtil =
            new JDBCUtil() {
                protected void delegatedLog(String logString) {
                    AbstractJdbcUsersRepository.this.getLogger().warn("AbstractJdbcUsersRepository: " + logString);
                }
            };

        // Get the data-source required.
        m_datasource = (DataSourceComponent)m_datasources.select(m_datasourceName);

        // Test the connection to the database, by getting the DatabaseMetaData.
        Connection conn = openConnection();
        try{
            DatabaseMetaData dbMetaData = conn.getMetaData();

            File sqlFile = null;

            try {
                sqlFile = AvalonContextUtilities.getFile(context, m_sqlFileName);
            } catch (Exception e) {
                getLogger().fatalError(e.getMessage(), e);
                throw e;
            }

            if (getLogger().isDebugEnabled()) {
                logBuffer =
                    new StringBuffer(256)
                            .append("Reading SQL resources from file: ")
                            .append(sqlFile.getAbsolutePath())
                            .append(", section ")
                            .append(this.getClass().getName())
                            .append(".");
                getLogger().debug(logBuffer.toString());
            }

            SqlResources sqlStatements = new SqlResources();
            sqlStatements.init(sqlFile, this.getClass().getName(),
                               conn, m_sqlParameters);

            // Create the SQL Strings to use for this table.
            // Fetches all Users from the db.
            m_getUsersSql = sqlStatements.getSqlString("select", true);

            // Get a user by lowercase name. (optional)
            // If not provided, the entire list is iterated to find a user.
            m_userByNameCaseInsensitiveSql =
                sqlStatements.getSqlString("selectByLowercaseName");

            // Insert, update and delete are not guaranteed to be case-insensitive
            // Will always be called with correct case in username..
            m_insertUserSql = sqlStatements.getSqlString("insert", true);
            m_updateUserSql = sqlStatements.getSqlString("update", true);
            m_deleteUserSql = sqlStatements.getSqlString("delete", true);

            // Creates a single table with "username" the Primary Key.
            m_createUserTableSql = sqlStatements.getSqlString("createTable", true);

            // Check if the required table exists. If not, create it.
            // The table name is defined in the SqlResources.
            String tableName = sqlStatements.getSqlString("tableName", true);

            // Need to ask in the case that identifiers are stored, ask the DatabaseMetaInfo.
            // NB this should work, but some drivers (eg mm MySQL)
            // don't return the right details, hence the hackery below.
            /*
            String tableName = m_tableName;
            if ( dbMetaData.storesLowerCaseIdentifiers() ) {
                tableName = tableName.toLowerCase(Locale.US);
            }
            else if ( dbMetaData.storesUpperCaseIdentifiers() ) {
                tableName = tableName.toUpperCase(Locale.US);
            }
            */

            // Try UPPER, lower, and MixedCase, to see if the table is there.
            if (! theJDBCUtil.tableExists(dbMetaData, tableName))
            {
                // Users table doesn't exist - create it.
                PreparedStatement createStatement = null;
                try {
                    createStatement =
                        conn.prepareStatement(m_createUserTableSql);
                    createStatement.execute();
                } finally {
                    theJDBCUtil.closeJDBCStatement(createStatement);
                }

                logBuffer =
                    new StringBuffer(128)
                            .append(this.getClass().getName())
                            .append(": Created table \'")
                            .append(tableName)
                            .append("\'.");
                getLogger().info(logBuffer.toString());
            } else {
                if (getLogger().isDebugEnabled()) {
                    getLogger().debug("Using table: " + tableName);
                }
            }

        }
        finally {
            theJDBCUtil.closeJDBCConnection( conn );
        }
    
protected java.util.IteratorlistAllUsers()
Returns a list populated with all of the Users in the repository.

return
an Iterator of JamesUsers.

        return getAllUsers().iterator();
    
protected java.util.ListlistUserNames()
Produces the complete list of User names, with correct case.

return
a List of Strings representing user names.

        Collection users = getAllUsers();
        List userNames = new ArrayList(users.size());
        for (Iterator it = users.iterator(); it.hasNext(); ) {
            userNames.add(((User)it.next()).getUserName());
        }
        users.clear();
        return userNames;
    
private java.sql.ConnectionopenConnection()
Opens a connection, throwing a runtime exception if a SQLException is encountered in the process.

return
the new connection

        try {
            return m_datasource.getConnection();
        }
        catch (SQLException sqle) {
            throw new CascadingRuntimeException(
                "An exception occurred getting a database connection.", sqle);
        }
    
protected abstract org.apache.james.services.UserreadUserFromResultSet(java.sql.ResultSet rsUsers)
Reads properties for a User from an open ResultSet. Subclass implementations of this method must have knowledge of the fields presented by the "select" and "selectByLowercaseName" SQL statements. These implemenations may generate a subclass-specific User instance.

param
rsUsers A ResultSet with a User record in the current row.
return
A User instance
throws
SQLException if an exception occurs reading from the ResultSet

public voidservice(org.apache.avalon.framework.service.ServiceManager componentManager)

see
org.apache.avalon.framework.service.Serviceable#compose(ServiceManager)

        StringBuffer logBuffer = null;
        if (getLogger().isDebugEnabled())
        {
            logBuffer =
                new StringBuffer(64)
                        .append(this.getClass().getName())
                        .append(".compose()");
            getLogger().debug( logBuffer.toString() );
        }

        m_datasources =
            (DataSourceSelector)componentManager.lookup( DataSourceSelector.ROLE );
    
protected abstract voidsetUserForInsertStatement(org.apache.james.services.User user, java.sql.PreparedStatement userInsert)
Set parameters of a PreparedStatement object with property values from a User instance. Implementations of this method have knowledge of the parameter ordering of the "insert" SQL statement definition.

param
user a User instance, which should be an implementation class which is handled by this Repostory implementation.
param
userInsert a PreparedStatement initialised with SQL taken from the "insert" SQL definition.
throws
SQLException if an exception occurs while setting parameter values.

protected abstract voidsetUserForUpdateStatement(org.apache.james.services.User user, java.sql.PreparedStatement userUpdate)
Set parameters of a PreparedStatement object with property values from a User instance. Implementations of this method have knowledge of the parameter ordering of the "update" SQL statement definition.

param
user a User instance, which should be an implementation class which is handled by this Repostory implementation.
param
userUpdate a PreparedStatement initialised with SQL taken from the "update" SQL definition.
throws
SQLException if an exception occurs while setting parameter values.