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

    private final List<UserPropertyProvider> providers = new ArrayList<>();

    public HybridUserPropertyProvider()
    {
        // Migrate user provider properties
        JiveGlobals.migrateProperty( "hybridUserPropertyProvider.primaryProvider.className" );
        JiveGlobals.migrateProperty( "hybridUserPropertyProvider.secondaryProvider.className" );
        JiveGlobals.migrateProperty( "hybridUserPropertyProvider.tertiaryProvider.className" );

        // Load primary, secondary, and tertiary user providers.
        final UserPropertyProvider primary = MappedUserPropertyProvider.instantiate( "hybridUserPropertyProvider.primaryProvider.className" );
        if ( primary != null )
        {
            providers.add( primary );
        }
        final UserPropertyProvider secondary = MappedUserPropertyProvider.instantiate( "hybridUserPropertyProvider.secondaryProvider.className" );
        if ( secondary != null )
        {
            providers.add( secondary );
        }
        final UserPropertyProvider tertiary = MappedUserPropertyProvider.instantiate( "hybridUserPropertyProvider.tertiaryProvider.className" );
        if ( tertiary != null )
        {
            providers.add( tertiary );
        }

        // Verify that there's at least one provider available.
        if ( providers.isEmpty() )
        {
            Log.error( "At least one UserPropertyProvider must be specified via openfire.xml or the system properties!" );
        }
    }

    /**
     * Returns the properties from the first provider that returns a non-empty collection.
     *
     * When none of the providers provide properties an empty collection is returned.
     *
     * @param username The identifier of the user (cannot be null or empty).
     * @return A collection, possibly empty, never null.
     */
    @Override
    public Map<String, String> loadProperties( String username )
    {
        for ( final UserPropertyProvider provider : providers )
        {
            try
            {
                final Map<String, String> properties = provider.loadProperties( username );
                if ( !properties.isEmpty() )
                {
                    return properties;
                }
            }
            catch ( UserNotFoundException e )
            {
                // User not in this provider. Try other providers;
            }
        }
        return Collections.emptyMap();
    }

    /**
     * Returns a property from the first provider that returns a non-null value.
     *
     * This method will return null when the desired property was not defined in any provider.
     *
     * @param username The identifier of the user (cannot be null or empty).
     * @param propName The property name (cannot be null or empty).
     * @return The property value (possibly null).
     */
    @Override
    public String loadProperty( String username, String propName )
    {
        for ( final UserPropertyProvider provider : providers )
        {
            try
            {
                final String property = provider.loadProperty( username, propName );
                if ( property != null )
                {
                    return property;
                }
            }
            catch ( UserNotFoundException e )
            {
                // User not in this provider. Try other providers;
            }
        }
        return null;
    }

    /**
     * Adds a new property, updating a previous property value if one already exists.
     *
     * Note that the implementation of this method is equal to that of {@link #updateProperty(String, String, String)}.
     *
     * First, tries to find a provider that has the property for the provided user. If that provider is read-only, an
     * UnsupportedOperationException is thrown. If the provider is not read-only, the existing property value will be
     * updated.
     *
     * When the property is not defined in any provider, it will be added in the first non-read-only provider.
     *
     * When all providers are read-only, an UnsupportedOperationException is thrown.
     *
     * @param username  The identifier of the user (cannot be null or empty).
     * @param propName  The property name (cannot be null or empty).
     * @param propValue The property value (cannot be null).
     */
    @Override
    public void insertProperty( String username, String propName, String propValue ) throws UnsupportedOperationException
    {
        updateProperty( username, propName, propValue );
    }

    /**
     * Updates a property (or adds a new property when the property does not exist).
     *
     * Note that the implementation of this method is equal to that of {@link #insertProperty(String, String, String)}.
     *
     * First, tries to find a provider that has the property for the provided user. If that provider is read-only, an
     * UnsupportedOperationException is thrown. If the provider is not read-only, the existing property value will be
     * updated.
     *
     * When the property is not defined in any provider, it will be added in the first non-read-only provider.
     *
     * When all providers are read-only, an UnsupportedOperationException is thrown.
     *
     * @param username  The identifier of the user (cannot be null or empty).
     * @param propName  The property name (cannot be null or empty).
     * @param propValue The property value (cannot be null).
     */
    @Override
    public void updateProperty( String username, String propName, String propValue ) throws UnsupportedOperationException
    {<FILL_FUNCTION_BODY>}

    /**
     * Removes a property from all non-read-only providers.
     *
     * @param username The identifier of the user (cannot be null or empty).
     * @param propName The property name (cannot be null or empty).
     */
    @Override
    public void deleteProperty( String username, String propName ) throws UnsupportedOperationException
    {
        // all providers are read-only
        if ( isReadOnly() )
        {
            throw new UnsupportedOperationException();
        }

        for ( final UserPropertyProvider provider : providers )
        {
            if ( provider.isReadOnly() )
            {
                continue;
            }

            try
            {
                provider.deleteProperty( username, propName );
            }
            catch ( UserNotFoundException e )
            {
                // User not in this provider. Try other providers;
            }
        }
    }

    /**
     * Returns whether <em>all</em> backing providers are read-only. When read-only, properties can not be created,
     * deleted, or modified. If at least one provider is not read-only, this method returns false.
     *
     * @return true when all backing providers are read-only, otherwise false.
     */
    @Override
    public boolean isReadOnly()
    {
        // TODO Make calls concurrent for improved throughput.
        for ( final UserPropertyProvider provider : providers )
        {
            // If at least one provider is not readonly, neither is this proxy.
            if ( !provider.isReadOnly() )
            {
                return false;
            }
        }

        return true;
    }
}

        for ( final UserPropertyProvider provider : providers )
        {
            try
            {
                if ( provider.loadProperty( username, propName ) != null )
                {
                    provider.updateProperty( username, propName, propValue );
                    return;
                }
            }
            catch ( UserNotFoundException e )
            {
                // User not in this provider. Try other providers;
            }
        }

        for ( final UserPropertyProvider provider : providers )
        {
            try
            {
                if ( !provider.isReadOnly() )
                {
                    provider.insertProperty( username, propName, propValue );
                    return;
                }
            }
            catch ( UserNotFoundException e )
            {
                // User not in this provider. Try other providers;
            }
        }
        throw new UnsupportedOperationException();
    
<no_super_class>