class JDBCUserPropertyProvider implements UserPropertyProvider
{
    private static final Logger Log = LoggerFactory.getLogger( JDBCUserPropertyProvider.class );

    private String loadPropertySQL;
    private String loadPropertiesSQL;
    private String connectionString;

    private boolean useConnectionProvider;

    /**
     * Constructs a new JDBC user property provider.
     */
    public JDBCUserPropertyProvider()
    {
        // Convert XML based provider setup to Database based
        JiveGlobals.migrateProperty( "jdbcUserPropertyProvider.driver" );
        JiveGlobals.migrateProperty( "jdbcUserPropertyProvider.connectionString" );
        JiveGlobals.migrateProperty( "jdbcUserPropertyProvider.loadPropertySQL" );
        JiveGlobals.migrateProperty( "jdbcUserPropertyProvider.loadPropertiesSQL" );

        useConnectionProvider = JiveGlobals.getBooleanProperty( "jdbcUserProvider.useConnectionProvider" );

        // Load the JDBC driver and connection string.
        if ( !useConnectionProvider )
        {
            String jdbcDriver = JiveGlobals.getProperty( "jdbcUserPropertyProvider.driver" );
            try
            {
                Class.forName( jdbcDriver ).newInstance();
            }
            catch ( Exception e )
            {
                Log.error( "Unable to load JDBC driver: " + jdbcDriver, e );
                return;
            }
            connectionString = JiveGlobals.getProperty( "jdbcProvider.connectionString" );
        }

        // Load database statements for user data.
        loadPropertySQL = JiveGlobals.getProperty( "jdbcUserPropertyProvider.loadPropertySQL" );
        loadPropertiesSQL = JiveGlobals.getProperty( "jdbcUserPropertyProvider.loadPropertiesSQL" );
    }

    /**
     * XMPP disallows some characters in identifiers, requiring them to be escaped.
     *
     * This implementation assumes that the database returns properly escaped identifiers,
     * but can apply escaping by setting the value of the 'jdbcUserPropertyProvider.isEscaped'
     * property to 'false'.
     *
     * @return 'false' if this implementation needs to escape database content before processing.
     */
    protected boolean assumePersistedDataIsEscaped()
    {
        return JiveGlobals.getBooleanProperty( "jdbcUserPropertyProvider.isEscaped", true );
    }

    private Connection getConnection() throws SQLException
    {
        if ( useConnectionProvider )
        {
            return DbConnectionManager.getConnection();
        }
        else
        {
            return DriverManager.getConnection( connectionString );
        }
    }

    @Override
    public Map<String, String> loadProperties( String username ) throws UnsupportedOperationException
    {
        Connection con = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;

        // OF-1837: When the database does not hold escaped data, our query should use unescaped values in the 'where' clause.
        final String queryValue = assumePersistedDataIsEscaped() ? username : JID.unescapeNode( username );

        try
        {
            con = getConnection();
            pstmt = con.prepareStatement( loadPropertiesSQL );
            pstmt.setString( 1, queryValue );
            rs = pstmt.executeQuery();

            final Map<String, String> result = new HashMap<>();
            while ( rs.next() )
            {
                final String propName = rs.getString( 1 );
                final String propValue = rs.getString( 2 );
                result.put( propName, propValue );
            }
            return result;
        }
        catch ( Exception e )
        {
            throw new UnsupportedOperationException( e );
        }
        finally
        {
            DbConnectionManager.closeConnection( rs, pstmt, con );
        }
    }

    @Override
    public String loadProperty( String username, String propName )
    {<FILL_FUNCTION_BODY>}

    @Override
    public void insertProperty( String username, String propName, String propValue ) throws UnsupportedOperationException
    {
        throw new UnsupportedOperationException();
    }

    @Override
    public void updateProperty( String username, String propName, String propValue ) throws UnsupportedOperationException
    {
        throw new UnsupportedOperationException();
    }

    @Override
    public void deleteProperty( String username, String propName ) throws UnsupportedOperationException
    {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean isReadOnly()
    {
        return true;
    }
}

        Connection con = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;

        // OF-1837: When the database does not hold escaped data, our query should use unescaped values in the 'where' clause.
        final String queryValue = assumePersistedDataIsEscaped() ? username : JID.unescapeNode( username );

        try
        {
            con = getConnection();
            pstmt = con.prepareStatement( loadPropertySQL );
            pstmt.setString( 1, queryValue );
            pstmt.setString( 2, propName );
            rs = pstmt.executeQuery();

            if ( rs.next() )
            {
                return rs.getString( 1 );
            }

            return null;
        }
        catch ( Exception e )
        {
            throw new UnsupportedOperationException( e );
        }
        finally
        {
            DbConnectionManager.closeConnection( rs, pstmt, con );
        }
    
<no_super_class>