private Collection<String> getUsernames(int startIndex, int numResults) {
        List<String> usernames = new ArrayList<>(500);
        Connection con = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            con = DbConnectionManager.getConnection();
            if ((startIndex==0) && (numResults==Integer.MAX_VALUE))
            {
                // MSSQL differentiates between client-cursored and server-cursored result sets. For server-cursored result
                // sets, the fetch buffer and scroll window are the same size (as opposed to fetch buffer containing all
                // the rows). To hint that a server-cursored result set is desired, it should be configured to be 'forward
                // only' as well as 'read only'.
                pstmt = con.prepareStatement(ALL_USERS, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
                // Set the fetch size. This will prevent some JDBC drivers from trying
                // to load the entire result set into memory.
                DbConnectionManager.setFetchSize(pstmt, 500);
                pstmt.setFetchDirection(ResultSet.FETCH_FORWARD);
                rs = pstmt.executeQuery();
                while (rs.next()) {
                    usernames.add(rs.getString(1));
                }
            }
            else {
                pstmt = DbConnectionManager.createScrollablePreparedStatement(con, ALL_USERS);
                DbConnectionManager.limitRowsAndFetchSize(pstmt, startIndex, numResults);
                rs = pstmt.executeQuery();
                DbConnectionManager.scrollResultSet(rs, startIndex);
                int count = 0;
                while (rs.next() && count < numResults) {
                    usernames.add(rs.getString(1));
                    count++;
                }
            }
            if (Log.isDebugEnabled()) {
                   Log.debug("Results: " + usernames.size());
                   LogResults(usernames);
            }
        }
        catch (SQLException e) {
            Log.error(e.getMessage(), e);
        }
        finally {
            DbConnectionManager.closeConnection(rs, pstmt, con);
        }
        return usernames;
    }
Default implementation of the UserProvider interface, which reads and writes data
from the {@code ofUser} database table.<p>

Passwords can be stored as plain text, or encrypted using Blowfish. The
encryption/decryption key is stored as the Openfire property {@code passwordKey},
which is automatically created on first-time use. It's critical that the password key
not be changed once created, or existing passwords will be lost. By default
passwords will be stored encrypted. Plain-text password storage can be enabled
by setting the Openfire property {@code user.usePlainPassword} to {@code true}.

@author Matt Tucker
xmppserver/src/main/java/org/jivesoftware/openfire/user/DefaultUserProvider.java