package org.apache.archiva.configuration;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

import org.apache.archiva.configuration.functors.ProxyConnectorConfigurationOrderComparator;
import org.apache.archiva.configuration.io.registry.ConfigurationRegistryReader;
import org.apache.archiva.configuration.io.registry.ConfigurationRegistryWriter;
import org.apache.archiva.policies.AbstractUpdatePolicy;
import org.apache.archiva.policies.CachedFailuresPolicy;
import org.apache.archiva.policies.ChecksumPolicy;
import org.apache.archiva.policies.DownloadErrorPolicy;
import org.apache.archiva.policies.Policy;
import org.apache.archiva.policies.PostDownloadPolicy;
import org.apache.archiva.policies.PreDownloadPolicy;
import org.apache.archiva.redback.components.evaluator.DefaultExpressionEvaluator;
import org.apache.archiva.redback.components.evaluator.EvaluatorException;
import org.apache.archiva.redback.components.evaluator.ExpressionEvaluator;
import org.apache.archiva.redback.components.evaluator.sources.SystemPropertyExpressionSource;
import org.apache.archiva.redback.components.registry.Registry;
import org.apache.archiva.redback.components.registry.RegistryException;
import org.apache.archiva.redback.components.registry.RegistryListener;
import org.apache.archiva.redback.components.registry.commons.CommonsConfigurationRegistry;
import org.apache.archiva.redback.components.springutils.ComponentContainer;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.ListUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.configuration.BaseConfiguration;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.inject.Inject;
import javax.inject.Named;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

/**
 * <p>
 * Implementation of configuration holder that retrieves it from the registry.
 * </p>
 * <p>
 * The registry layers and merges the 2 configuration files: user, and application server.
 * </p>
 * <p>
 * Instead of relying on the model defaults, if the registry is empty a default configuration file is loaded and
 * applied from a resource. The defaults are not loaded into the registry as the lists (eg repositories) could no longer
 * be removed if that was the case.
 * </p>
 * <p>
 * When saving the configuration, it is saved to the location it was read from. If it was read from the defaults, it
 * will be saved to the user location.
 * However, if the configuration contains information from both sources, an exception is raised as this is currently
 * unsupported. The reason for this is that it is not possible to identify where to re-save elements, and can result
 * in list configurations (eg repositories) becoming inconsistent.
 * </p>
 * <p>
 * If the configuration is outdated, it will be upgraded when it is loaded. This is done by checking the version flag
 * before reading it from the registry.
 * </p>
 */
@Service("archivaConfiguration#default")
public class DefaultArchivaConfiguration
    implements ArchivaConfiguration, RegistryListener
{
    private Logger log = LoggerFactory.getLogger( DefaultArchivaConfiguration.class );

    /**
     * Plexus registry to read the configuration from.
     */
    @Inject
    @Named(value = "commons-configuration")
    private Registry registry;

    @Inject
    private ComponentContainer componentContainer;

    /**
     * The configuration that has been converted.
     */
    private Configuration configuration;

    /**
     * see #initialize
     *
     * @todo these don't strictly belong in here
     */
    private Map<String, PreDownloadPolicy> prePolicies;

    /**
     * see #initialize
     *
     * @todo these don't strictly belong in here
     */
    private Map<String, PostDownloadPolicy> postPolicies;

    /**
     * see #initialize
     *
     * @todo these don't strictly belong in here
     */
    private Map<String, DownloadErrorPolicy> downloadErrorPolicies;


    /**
     * see #initialize
     * default-value="${user.home}/.m2/archiva.xml"
     */
    private String userConfigFilename = "${user.home}/.m2/archiva.xml";

    /**
     * see #initialize
     * default-value="${appserver.base}/conf/archiva.xml"
     */
    private String altConfigFilename = "${appserver.base}/conf/archiva.xml";

    /**
     * Configuration Listeners we've registered.
     */
    private Set<ConfigurationListener> listeners = new HashSet<>();

    /**
     * Registry Listeners we've registered.
     */
    private Set<RegistryListener> registryListeners = new HashSet<>();

    /**
     * Boolean to help determine if the configuration exists as a result of pulling in
     * the default-archiva.xml
     */
    private boolean isConfigurationDefaulted = false;

    private static final String KEY = "org.apache.archiva";

    // Section used for default only configuration
    private static final String KEY_DEFAULT_ONLY = "org.apache.archiva_default";

    @Override
    public Configuration getConfiguration()
    {
        return loadConfiguration();
    }

    private synchronized Configuration loadConfiguration()
    {
        if ( configuration == null )
        {
            configuration = load();
            configuration = unescapeExpressions( configuration );
            if ( isConfigurationDefaulted )
            {
                configuration = checkRepositoryLocations( configuration );
            }
        }

        return configuration;
    }

    private boolean hasConfigVersionChanged(Configuration current, Registry defaultOnlyConfiguration) {
        return current==null || current.getVersion()==null ||
                !current.getVersion().trim().equals(defaultOnlyConfiguration.getString("version","").trim());
    }

    @SuppressWarnings("unchecked")
    private Configuration load()
    {
        // TODO: should this be the same as section? make sure unnamed sections still work (eg, sys properties)
        Registry subset = registry.getSubset( KEY );
        if ( subset.getString( "version" ) == null )
        {
            // a little autodetection of v1, even if version is omitted (this was previously allowed)
            if ( subset.getSubset( "repositoryScanning" ).isEmpty() )
            {
                // only for empty, or v < 1
                subset = readDefaultConfiguration();
            }
        }

        Configuration config = new ConfigurationRegistryReader().read( subset );


        config.getRepositoryGroups();
        config.getRepositoryGroupsAsMap();
        if ( !config.getRepositories().isEmpty() )
        {
            for ( V1RepositoryConfiguration r : config.getRepositories() )
            {
                r.setScanned( r.isIndexed() );

                if ( StringUtils.startsWith( r.getUrl(), "file://" ) )
                {
                    r.setLocation( r.getUrl().substring( 7 ) );
                    config.addManagedRepository( r );
                }
                else if ( StringUtils.startsWith( r.getUrl(), "file:" ) )
                {
                    r.setLocation( r.getUrl().substring( 5 ) );
                    config.addManagedRepository( r );
                }
                else if ( StringUtils.isEmpty( r.getUrl() ) )
                {
                    // in case of empty url we can consider it as a managed one
                    // check if location is null
                    //file://${appserver.base}/repositories/${id}
                    if ( StringUtils.isEmpty( r.getLocation() ) )
                    {
                        r.setLocation( "file://${appserver.base}/repositories/" + r.getId() );
                    }
                    config.addManagedRepository( r );
                }
                else
                {
                    RemoteRepositoryConfiguration repo = new RemoteRepositoryConfiguration();
                    repo.setId( r.getId() );
                    repo.setLayout( r.getLayout() );
                    repo.setName( r.getName() );
                    repo.setUrl( r.getUrl() );
                    config.addRemoteRepository( repo );
                }
            }

            // Prevent duplicate repositories from showing up.
            config.getRepositories().clear();

            registry.removeSubset( KEY + ".repositories" );
        }

        if ( !CollectionUtils.isEmpty( config.getRemoteRepositories() ) )
        {
            List<RemoteRepositoryConfiguration> remoteRepos = config.getRemoteRepositories();
            for ( RemoteRepositoryConfiguration repo : remoteRepos )
            {
                // [MRM-582] Remote Repositories with empty <username> and <password> fields shouldn't be created in configuration.
                if ( StringUtils.isBlank( repo.getUsername() ) )
                {
                    repo.setUsername( null );
                }

                if ( StringUtils.isBlank( repo.getPassword() ) )
                {
                    repo.setPassword( null );
                }
            }
        }

        if ( !config.getProxyConnectors().isEmpty() )
        {
            // Fix Proxy Connector Settings.

            // Create a copy of the list to read from (to prevent concurrent modification exceptions)
            List<ProxyConnectorConfiguration> proxyConnectorList = new ArrayList<>( config.getProxyConnectors() );
            // Remove the old connector list.
            config.getProxyConnectors().clear();

            for ( ProxyConnectorConfiguration connector : proxyConnectorList )
            {
                // Fix policies
                boolean connectorValid = true;

                Map<String, String> policies = new HashMap<>();
                // Make copy of policies
                policies.putAll( connector.getPolicies() );
                // Clear out policies
                connector.getPolicies().clear();

                // Work thru policies. cleaning them up.
                for ( Entry<String, String> entry : policies.entrySet() )
                {
                    String policyId = entry.getKey();
                    String setting = entry.getValue();

                    // Upgrade old policy settings.
                    if ( "releases".equals( policyId ) || "snapshots".equals( policyId ) )
                    {
                        if ( "ignored".equals( setting ) )
                        {
                            setting = AbstractUpdatePolicy.ALWAYS;
                        }
                        else if ( "disabled".equals( setting ) )
                        {
                            setting = AbstractUpdatePolicy.NEVER;
                        }
                    }
                    else if ( "cache-failures".equals( policyId ) )
                    {
                        if ( "ignored".equals( setting ) )
                        {
                            setting = CachedFailuresPolicy.NO;
                        }
                        else if ( "cached".equals( setting ) )
                        {
                            setting = CachedFailuresPolicy.YES;
                        }
                    }
                    else if ( "checksum".equals( policyId ) )
                    {
                        if ( "ignored".equals( setting ) )
                        {
                            setting = ChecksumPolicy.IGNORE;
                        }
                    }

                    // Validate existance of policy key.
                    if ( policyExists( policyId ) )
                    {
                        Policy policy = findPolicy( policyId );
                        // Does option exist?
                        if ( !policy.getOptions().contains( setting ) )
                        {
                            setting = policy.getDefaultOption();
                        }
                        connector.addPolicy( policyId, setting );
                    }
                    else
                    {
                        // Policy key doesn't exist. Don't add it to golden version.
                        log.warn( "Policy [{}] does not exist.", policyId );
                    }
                }

                if ( connectorValid )
                {
                    config.addProxyConnector( connector );
                }
            }

            // Normalize the order fields in the proxy connectors.
            Map<String, java.util.List<ProxyConnectorConfiguration>> proxyConnectorMap =
                config.getProxyConnectorAsMap();

            for ( List<ProxyConnectorConfiguration> connectors : proxyConnectorMap.values() )
            {
                // Sort connectors by order field.
                Collections.sort( connectors, ProxyConnectorConfigurationOrderComparator.getInstance() );

                // Normalize the order field values.
                int order = 1;
                for ( ProxyConnectorConfiguration connector : connectors )
                {
                    connector.setOrder( order++ );
                }
            }
        }



        return config;
    }

    /*
     * Updates the checkpath list for repositories.
     *
     * We are replacing existing ones and adding new ones. This allows to update the list with new releases.
     *
     * We are also updating existing remote repositories, if they exist already.
     *
     * This update method should only be called, if the config version changes to avoid overwriting
     * user repository settings all the time.
     */
    private void updateCheckPathDefaults(Configuration config, Registry defaultConfiguration) {
        List<RepositoryCheckPath> existingCheckPathList = config.getArchivaDefaultConfiguration().getDefaultCheckPaths();
        HashMap<String, RepositoryCheckPath> existingCheckPaths = new HashMap<>();
        HashMap<String, RepositoryCheckPath> newCheckPaths = new HashMap<>();
        for (RepositoryCheckPath path : config.getArchivaDefaultConfiguration().getDefaultCheckPaths()) {
            existingCheckPaths.put(path.getUrl(), path);
        }
        List defaultCheckPathsSubsets = defaultConfiguration.getSubsetList("archivaDefaultConfiguration.defaultCheckPaths.defaultCheckPath" );
        for ( Iterator i = defaultCheckPathsSubsets.iterator(); i.hasNext(); )
        {
            RepositoryCheckPath v = readRepositoryCheckPath( (Registry) i.next() );
            if (existingCheckPaths.containsKey(v.getUrl())) {
                existingCheckPathList.remove(existingCheckPaths.get(v.getUrl()));
            }
            existingCheckPathList.add(v);
            newCheckPaths.put(v.getUrl(), v);
        }
        // Remote repositories update
        for (RemoteRepositoryConfiguration remoteRepositoryConfiguration : config.getRemoteRepositories()) {
            String url = remoteRepositoryConfiguration.getUrl().toLowerCase();
            if (newCheckPaths.containsKey(url)) {
                String currentPath = remoteRepositoryConfiguration.getCheckPath();
                String newPath = newCheckPaths.get(url).getPath();
                log.info("Updating connection check path for repository {}, from '{}' to '{}'.", remoteRepositoryConfiguration.getId(),
                        currentPath, newPath);
                remoteRepositoryConfiguration.setCheckPath(newPath);
            }
        }
    }

    private RepositoryCheckPath readRepositoryCheckPath( Registry registry )
    {
        RepositoryCheckPath value = new RepositoryCheckPath();

        String url = registry.getString( "url", value.getUrl() );

        value.setUrl( url );
        String path = registry.getString( "path", value.getPath() );
        value.setPath( path );
        return value;
    }

    private Policy findPolicy( String policyId )
    {
        if ( MapUtils.isEmpty( prePolicies ) )
        {
            log.error( "No PreDownloadPolicies found!" );
            return null;
        }

        if ( MapUtils.isEmpty( postPolicies ) )
        {
            log.error( "No PostDownloadPolicies found!" );
            return null;
        }

        Policy policy;

        policy = prePolicies.get( policyId );
        if ( policy != null )
        {
            return policy;
        }

        policy = postPolicies.get( policyId );
        if ( policy != null )
        {
            return policy;
        }

        policy = downloadErrorPolicies.get( policyId );
        if ( policy != null )
        {
            return policy;
        }

        return null;
    }

    private boolean policyExists( String policyId )
    {
        if ( MapUtils.isEmpty( prePolicies ) )
        {
            log.error( "No PreDownloadPolicies found!" );
            return false;
        }

        if ( MapUtils.isEmpty( postPolicies ) )
        {
            log.error( "No PostDownloadPolicies found!" );
            return false;
        }

        return ( prePolicies.containsKey( policyId ) || postPolicies.containsKey( policyId )
            || downloadErrorPolicies.containsKey( policyId ) );
    }

    private Registry readDefaultConfiguration()
    {
        // if it contains some old configuration, remove it (Archiva 0.9)
        registry.removeSubset( KEY );

        try
        {
            registry.addConfigurationFromResource( "org/apache/archiva/configuration/default-archiva.xml", KEY );
            this.isConfigurationDefaulted = true;
        }
        catch ( RegistryException e )
        {
            throw new ConfigurationRuntimeException(
                "Fatal error: Unable to find the built-in default configuration and load it into the registry", e );
        }
        return registry.getSubset( KEY );
    }

    /*
     * Reads the default only configuration into a special prefix. This allows to check for changes
     * of the default configuration.
     */
    private Registry readDefaultOnlyConfiguration()
    {
        registry.removeSubset(KEY_DEFAULT_ONLY);
        try
        {
            registry.addConfigurationFromResource( "org/apache/archiva/configuration/default-archiva.xml", KEY_DEFAULT_ONLY);
        }
        catch ( RegistryException e )
        {
            throw new ConfigurationRuntimeException(
                    "Fatal error: Unable to find the built-in default configuration and load it into the registry", e );
        }
        return registry.getSubset(KEY_DEFAULT_ONLY);
    }

    @SuppressWarnings("unchecked")
    @Override
    public synchronized void save( Configuration configuration )
        throws IndeterminateConfigurationException, RegistryException
    {
        Registry section = registry.getSection( KEY + ".user" );
        Registry baseSection = registry.getSection( KEY + ".base" );
        if ( section == null )
        {
            section = baseSection;
            if ( section == null )
            {
                section = createDefaultConfigurationFile();
            }
        }
        else if ( baseSection != null )
        {
            Collection<String> keys = baseSection.getKeys();
            boolean foundList = false;
            for ( Iterator<String> i = keys.iterator(); i.hasNext() && !foundList; )
            {
                String key = i.next();

                // a little aggressive with the repositoryScanning and databaseScanning - should be no need to split
                // that configuration
                if ( key.startsWith( "repositories" ) //
                    || key.startsWith( "proxyConnectors" ) //
                    || key.startsWith( "networkProxies" ) //
                    || key.startsWith( "repositoryScanning" ) //
                    || key.startsWith( "remoteRepositories" ) //
                    || key.startsWith( "managedRepositories" ) //
                    || key.startsWith( "repositoryGroups" ) ) //
                {
                    foundList = true;
                }
            }

            if ( foundList )
            {
                this.configuration = null;

                throw new IndeterminateConfigurationException(
                    "Configuration can not be saved when it is loaded from two sources" );
            }
        }

        // escape all cron expressions to handle ','
        escapeCronExpressions( configuration );

        // [MRM-661] Due to a bug in the modello registry writer, we need to take these out by hand. They'll be put back by the writer.
        if ( section != null )
        {
            if ( configuration.getManagedRepositories().isEmpty() )
            {
                section.removeSubset( "managedRepositories" );
            }
            if ( configuration.getRemoteRepositories().isEmpty() )
            {
                section.removeSubset( "remoteRepositories" );

            }
            if ( configuration.getProxyConnectors().isEmpty() )
            {
                section.removeSubset( "proxyConnectors" );
            }
            if ( configuration.getNetworkProxies().isEmpty() )
            {
                section.removeSubset( "networkProxies" );
            }
            if ( configuration.getLegacyArtifactPaths().isEmpty() )
            {
                section.removeSubset( "legacyArtifactPaths" );
            }
            if ( configuration.getRepositoryGroups().isEmpty() )
            {
                section.removeSubset( "repositoryGroups" );
            }
            if ( configuration.getRepositoryScanning() != null )
            {
                if ( configuration.getRepositoryScanning().getKnownContentConsumers().isEmpty() )
                {
                    section.removeSubset( "repositoryScanning.knownContentConsumers" );
                }
                if ( configuration.getRepositoryScanning().getInvalidContentConsumers().isEmpty() )
                {
                    section.removeSubset( "repositoryScanning.invalidContentConsumers" );
                }
            }
            if (configuration.getArchivaRuntimeConfiguration()!=null) {
                section.removeSubset("archivaRuntimeConfiguration.defaultCheckPaths");
            }

            new ConfigurationRegistryWriter().write( configuration, section );
            section.save();
        }



        this.configuration = unescapeExpressions( configuration );

        triggerEvent( ConfigurationEvent.SAVED );
    }

    private void escapeCronExpressions( Configuration configuration )
    {
        for ( ManagedRepositoryConfiguration c : configuration.getManagedRepositories() )
        {
            c.setRefreshCronExpression( escapeCronExpression( c.getRefreshCronExpression() ) );
        }
    }

    private Registry createDefaultConfigurationFile()
        throws RegistryException
    {
        // TODO: may not be needed under commons-configuration 1.4 - check

        String contents = "<configuration />";

        String fileLocation = userConfigFilename;

        if ( !writeFile( "user configuration", userConfigFilename, contents ) )
        {
            fileLocation = altConfigFilename;
            if ( !writeFile( "alternative configuration", altConfigFilename, contents ) )
            {
                throw new RegistryException(
                    "Unable to create configuration file in either user [" + userConfigFilename + "] or alternative ["
                        + altConfigFilename
                        + "] locations on disk, usually happens when not allowed to write to those locations." );
            }
        }

        // olamy hackish I know :-)
        contents = "<configuration><xml fileName=\"" + fileLocation
            + "\" config-forceCreate=\"true\" config-name=\"org.apache.archiva.user\"/>" + "</configuration>";

        ( (CommonsConfigurationRegistry) registry ).setProperties( contents );

        registry.initialize();

        for ( RegistryListener regListener : registryListeners )
        {
            addRegistryChangeListener( regListener );
        }

        triggerEvent( ConfigurationEvent.SAVED );

        Registry section = registry.getSection( KEY + ".user" );
        return section == null ? new CommonsConfigurationRegistry( new BaseConfiguration() ) : section;
    }

    /**
     * Attempts to write the contents to a file, if an IOException occurs, return false.
     * <p/>
     * The file will be created if the directory to the file exists, otherwise this will return false.
     *
     * @param filetype the filetype (freeform text) to use in logging messages when failure to write.
     * @param path     the path to write to.
     * @param contents the contents to write.
     * @return true if write successful.
     */
    private boolean writeFile( String filetype, String path, String contents )
    {
        File file = new File( path );

        try
        {
            // Check parent directory (if it is declared)
            if ( file.getParentFile() != null )
            {
                // Check that directory exists
                if ( !file.getParentFile().isDirectory() )
                {
                    // Directory to file must exist for file to be created
                    return false;
                }
            }

            FileUtils.writeStringToFile( file, contents, "UTF-8" );
            return true;
        }
        catch ( IOException e )
        {
            log.error( "Unable to create " + filetype + " file: " + e.getMessage(), e );
            return false;
        }
    }

    private void triggerEvent( int type )
    {
        ConfigurationEvent evt = new ConfigurationEvent( type );
        for ( ConfigurationListener listener : listeners )
        {
            listener.configurationEvent( evt );
        }
    }

    @Override
    public void addListener( ConfigurationListener listener )
    {
        if ( listener == null )
        {
            return;
        }

        listeners.add( listener );
    }

    @Override
    public void removeListener( ConfigurationListener listener )
    {
        if ( listener == null )
        {
            return;
        }

        listeners.remove( listener );
    }


    @Override
    public void addChangeListener( RegistryListener listener )
    {
        addRegistryChangeListener( listener );

        // keep track for later
        registryListeners.add( listener );
    }

    private void addRegistryChangeListener( RegistryListener listener )
    {
        Registry section = registry.getSection( KEY + ".user" );
        if ( section != null )
        {
            section.addChangeListener( listener );
        }
        section = registry.getSection( KEY + ".base" );
        if ( section != null )
        {
            section.addChangeListener( listener );
        }
    }

    @Override
    public void removeChangeListener( RegistryListener listener )
    {
        boolean removed = registryListeners.remove( listener );
        log.debug( "RegistryListener: '{}' removed {}", listener, removed );

        Registry section = registry.getSection( KEY + ".user" );
        if ( section != null )
        {
            section.removeChangeListener( listener );
        }
        section = registry.getSection( KEY + ".base" );
        if ( section != null )
        {
            section.removeChangeListener( listener );
        }

    }

    @PostConstruct
    public void initialize()
    {

        this.postPolicies = componentContainer.buildMapWithRole( PostDownloadPolicy.class );
        this.prePolicies = componentContainer.buildMapWithRole( PreDownloadPolicy.class );
        this.downloadErrorPolicies = componentContainer.buildMapWithRole( DownloadErrorPolicy.class );
        // Resolve expressions in the userConfigFilename and altConfigFilename
        try
        {
            ExpressionEvaluator expressionEvaluator = new DefaultExpressionEvaluator();
            expressionEvaluator.addExpressionSource( new SystemPropertyExpressionSource() );
            String userConfigFileNameSysProps = System.getProperty( "archiva.user.configFileName" );
            if ( StringUtils.isNotBlank( userConfigFileNameSysProps ) )
            {
                userConfigFilename = userConfigFileNameSysProps;
            }
            else
            {
                userConfigFilename = expressionEvaluator.expand( userConfigFilename );
            }
            altConfigFilename = expressionEvaluator.expand( altConfigFilename );
            loadConfiguration();
            handleUpgradeConfiguration();
        }
        catch ( IndeterminateConfigurationException | RegistryException e )
        {
            throw new RuntimeException( "failed during upgrade from previous version" + e.getMessage(), e );
        }
        catch ( EvaluatorException e )
        {
            throw new RuntimeException(
                "Unable to evaluate expressions found in " + "userConfigFilename or altConfigFilename.", e );
        }
        registry.addChangeListener( this );
    }

    /**
     * Handle upgrade to newer version
     */
    private void handleUpgradeConfiguration()
        throws RegistryException, IndeterminateConfigurationException
    {

        List<String> dbConsumers = Arrays.asList( "update-db-artifact", "update-db-repository-metadata" );

        // remove database consumers if here
        List<String> intersec =
            ListUtils.intersection( dbConsumers, configuration.getRepositoryScanning().getKnownContentConsumers() );

        if ( !intersec.isEmpty() )
        {

            List<String> knowContentConsumers =
                new ArrayList<>( configuration.getRepositoryScanning().getKnownContentConsumers().size() );
            for ( String knowContentConsumer : configuration.getRepositoryScanning().getKnownContentConsumers() )
            {
                if ( !dbConsumers.contains( knowContentConsumer ) )
                {
                    knowContentConsumers.add( knowContentConsumer );
                }
            }

            configuration.getRepositoryScanning().setKnownContentConsumers( knowContentConsumers );
        }

        // ensure create-archiva-metadata is here
        if ( !configuration.getRepositoryScanning().getKnownContentConsumers().contains( "create-archiva-metadata" ) )
        {
            List<String> knowContentConsumers =
                new ArrayList<>( configuration.getRepositoryScanning().getKnownContentConsumers() );
            knowContentConsumers.add( "create-archiva-metadata" );
            configuration.getRepositoryScanning().setKnownContentConsumers( knowContentConsumers );
        }

        // ensure duplicate-artifacts is here
        if ( !configuration.getRepositoryScanning().getKnownContentConsumers().contains( "duplicate-artifacts" ) )
        {
            List<String> knowContentConsumers =
                new ArrayList<>( configuration.getRepositoryScanning().getKnownContentConsumers() );
            knowContentConsumers.add( "duplicate-artifacts" );
            configuration.getRepositoryScanning().setKnownContentConsumers( knowContentConsumers );
        }

        Registry defaultOnlyConfiguration = readDefaultOnlyConfiguration();
        // Currently we check only for configuration version change, not certain version numbers.
        if (hasConfigVersionChanged(configuration, defaultOnlyConfiguration)) {
            updateCheckPathDefaults(configuration, defaultOnlyConfiguration);
            String newVersion = defaultOnlyConfiguration.getString("version");
            if (newVersion==null) {
                throw new IndeterminateConfigurationException("The default configuration has no version information!");
            }
            configuration.setVersion(newVersion);
            try {
                save(configuration);
            } catch (IndeterminateConfigurationException e) {
                log.error("Error occured during configuration update to new version: {}", e.getMessage());
            } catch (RegistryException e) {
                log.error("Error occured during configuration update to new version: {}", e.getMessage());
            }
        }
    }

    @Override
    public void reload()
    {
        this.configuration = null;
        try
        {
            this.registry.initialize();
        }
        catch ( RegistryException e )
        {
            throw new ConfigurationRuntimeException( e.getMessage(), e );
        }
        this.initialize();
    }

    @Override
    public void beforeConfigurationChange( Registry registry, String propertyName, Object propertyValue )
    {
        // nothing to do here
    }

    @Override
    public synchronized void afterConfigurationChange( Registry registry, String propertyName, Object propertyValue )
    {
        configuration = null;
    }

    private String removeExpressions( String directory )
    {
        String value = StringUtils.replace( directory, "${appserver.base}",
                                            registry.getString( "appserver.base", "${appserver.base}" ) );
        value = StringUtils.replace( value, "${appserver.home}",
                                     registry.getString( "appserver.home", "${appserver.home}" ) );
        return value;
    }

    private String unescapeCronExpression( String cronExpression )
    {
        return StringUtils.replace( cronExpression, "\\,", "," );
    }

    private String escapeCronExpression( String cronExpression )
    {
        return StringUtils.replace( cronExpression, ",", "\\," );
    }

    private Configuration unescapeExpressions( Configuration config )
    {
        // TODO: for commons-configuration 1.3 only
        for ( ManagedRepositoryConfiguration c : config.getManagedRepositories() )
        {
            c.setLocation( removeExpressions( c.getLocation() ) );
            c.setRefreshCronExpression( unescapeCronExpression( c.getRefreshCronExpression() ) );
        }

        return config;
    }

    private Configuration checkRepositoryLocations( Configuration config )
    {
        // additional check for [MRM-789], ensure that the location of the default repositories 
        // are not installed in the server installation        
        for ( ManagedRepositoryConfiguration repo : (List<ManagedRepositoryConfiguration>) config.getManagedRepositories() )
        {
            String repoPath = repo.getLocation();
            File repoLocation = new File( repoPath );

            if ( repoLocation.exists() && repoLocation.isDirectory() && !repoPath.endsWith(
                "data/repositories/" + repo.getId() ) )
            {
                repo.setLocation( repoPath + "/data/repositories/" + repo.getId() );
            }
        }

        return config;
    }

    public String getUserConfigFilename()
    {
        return userConfigFilename;
    }

    public String getAltConfigFilename()
    {
        return altConfigFilename;
    }

    @Override
    public boolean isDefaulted()
    {
        return this.isConfigurationDefaulted;
    }

    public Registry getRegistry()
    {
        return registry;
    }

    public void setRegistry( Registry registry )
    {
        this.registry = registry;
    }


    public void setUserConfigFilename( String userConfigFilename )
    {
        this.userConfigFilename = userConfigFilename;
    }

    public void setAltConfigFilename( String altConfigFilename )
    {
        this.altConfigFilename = altConfigFilename;
    }
}
