/*
 * Copyright (C) 2005-2013 Alfresco Software Limited.
 *
 * This file is part of Alfresco
 *
 * Alfresco is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Alfresco is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Alfresco. If not, see .
 */
package org.alfresco.repo.config;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.alfresco.repo.cache.AbstractAsynchronouslyRefreshedCache;
import org.alfresco.repo.config.ConfigDataCache.ConfigData;
import org.alfresco.repo.config.xml.RepoXMLConfigService;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.extensions.config.ConfigDeployment;
import org.springframework.extensions.config.ConfigImpl;
import org.springframework.extensions.config.ConfigSection;
import org.springframework.extensions.config.ConfigService;
import org.springframework.extensions.config.evaluator.Evaluator;
import org.springframework.extensions.config.xml.elementreader.ConfigElementReader;
import com.sun.star.uno.RuntimeException;
/**
 * An innder class that uses the {@link RepoXMLConfigService config service} to asynchronously
 * refresh tenant config data.
 * 
 * Cluster-wide invalidation messages mean that we have to be very careful about only making
 * updates to caches when entries really change.  However, the nature of the {@link ConfigService}
 * hierarchy makes it very difficult to do this in a deterministic manner without performing
 * write locks that run across tenants.  By receiving asynchronous messages to refresh we no
 * longer have to rely on a cluster-aware cache.
 * 
 * @author Derek Hulley
 * @author Andy Hind
 * @since 4.1.5
 */
public class ConfigDataCache extends AbstractAsynchronouslyRefreshedCache
{
    private static Log logger = LogFactory.getLog(ConfigDataCache.class);
    
    private RepoXMLConfigService repoXMLConfigService;
    /**
     * Set the config service.  Depending on the order of injection, this might have to
     * be done by code.
     */
    public void setRepoXMLConfigService(RepoXMLConfigService repoXMLConfigService)
    {
        this.repoXMLConfigService = repoXMLConfigService;
    }
    @Override
    protected ConfigData buildCache(String tenantId)
    {
        if (logger.isDebugEnabled())
        {
            logger.debug("Received request to rebuild config data for tenant: " + tenantId);
        }
        ConfigData configData = repoXMLConfigService.getRepoConfig(tenantId);
        if (!(configData instanceof ImmutableConfigData))
        {
            throw new RuntimeException("ConfigData must be immutable for the cache.");
        }
        if (logger.isDebugEnabled())
        {
            logger.debug("Rebuilt config data for tenant: " + tenantId + " (" + configData + ").");
        }
        return configData;
    }
    
    /**
     * Data containing all a tenant's required UI configuration
     * 
     * @author various
     */
    public static class ConfigData
    {
        private ConfigImpl globalConfig;   
        private Map evaluators;
        private Map> sectionsByArea;
        private List sections;
        private Map elementReaders;
        
        private List configDeployments;
        
        public ConfigData()
        {
        }
        
        public ConfigImpl getGlobalConfig()
        {
            return globalConfig;
        }
        public void setGlobalConfig(ConfigImpl globalConfig)
        {
            this.globalConfig = globalConfig;
        }
        public Map getEvaluators()
        {
            return evaluators;
        }
        public void setEvaluators(Map evaluators)
        {
            this.evaluators = evaluators;
        }
        public Map> getSectionsByArea()
        {
            return sectionsByArea;
        }
        public void setSectionsByArea(Map> sectionsByArea)
        {
            this.sectionsByArea = sectionsByArea;
        }
        public List getSections()
        {
            return sections;
        }
        public void setSections(List sections)
        {
            this.sections = sections;
        }
        public Map getElementReaders()
        {
            return elementReaders;
        }
        public void setElementReaders(Map elementReaders)
        {
            this.elementReaders = elementReaders;
        }
        public List getConfigDeployments()
        {
            return configDeployments;
        }
        public void setConfigDeployments(List configDeployments)
        {
            this.configDeployments = configDeployments;
        }
    }
    
    /**
     * Immutable version of {@link ConfigData} to ensure cast-iron safety of data
     * being put into the caches.
     * 
     * @author Derek Hulley
     * @since 4.1.5
     */
    public static class ImmutableConfigData extends ConfigData
    {
        /**
         * Local variable to allow setter use during construction
         */
        private boolean locked = false;
        
        /**
         * Copy constructor that prevents any data from being changed.
         * 
         * @param configData            the config to copy
         */
        public ImmutableConfigData(ConfigData configData)
        {
            /*
             * Each member is copied or protected in some way to ensure immutability
             */
            
            List configDeployments = configData.getConfigDeployments();
            if (configDeployments != null)
            {
                List configDeploymentsLocked = Collections.unmodifiableList(configDeployments);
                setConfigDeployments(configDeploymentsLocked);
            }
            else
            {
                setConfigDeployments(Collections.emptyList());
            }
            
            Map elementReaders = configData.getElementReaders();
            if (elementReaders != null)
            {
                Map elementReadersLocked = Collections.unmodifiableMap(elementReaders);
                setElementReaders(elementReadersLocked);
            }
            else
            {
                setElementReaders(Collections.emptyMap());
            }
            
            Map evaluators = configData.getEvaluators();
            if (evaluators != null)
            {
                Map evaluatorsLocked = Collections.unmodifiableMap(evaluators);
                setEvaluators(evaluatorsLocked);
            }
            else
            {
                setEvaluators(Collections.emptyMap());
            }
            
            ConfigImpl globalConfig = configData.getGlobalConfig();
            ImmutableConfig globalConfigLocked = new ImmutableConfig(globalConfig);
            setGlobalConfig(globalConfigLocked);
            
            List sections = configData.getSections();
            if (sections != null)
            {
                List sectionsLocked = Collections.unmodifiableList(sections);
                setSections(sectionsLocked);
            }
            else
            {
                setSections(Collections.emptyList());
            }
            
            Map> sectionsByArea = configData.getSectionsByArea();
            if (sectionsByArea != null)
            {
                Map> sectionsByAreaLocked = Collections.unmodifiableMap(sectionsByArea);
                setSectionsByArea(sectionsByAreaLocked);
            }
            else
            {
                setSectionsByArea(Collections.>emptyMap());
            }
            
            // Now prevent setters from being used
            locked = true;
        }
        @Override
        public void setGlobalConfig(ConfigImpl globalConfig)
        {
            if (locked)
            {
                throw new IllegalStateException("ConfigData has been locked.");
            }
            super.setGlobalConfig(globalConfig);
        }
        @Override
        public void setEvaluators(Map evaluators)
        {
            if (locked)
            {
                throw new IllegalStateException("ConfigData has been locked.");
            }
            super.setEvaluators(evaluators);
        }
        @Override
        public void setSectionsByArea(Map> sectionsByArea)
        {
            if (locked)
            {
                throw new IllegalStateException("ConfigData has been locked.");
            }
            super.setSectionsByArea(sectionsByArea);
        }
        @Override
        public void setSections(List sections)
        {
            if (locked)
            {
                throw new IllegalStateException("ConfigData has been locked.");
            }
            super.setSections(sections);
        }
        @Override
        public void setElementReaders(Map elementReaders)
        {
            if (locked)
            {
                throw new IllegalStateException("ConfigData has been locked.");
            }
            super.setElementReaders(elementReaders);
        }
        @Override
        public void setConfigDeployments(List configDeployments)
        {
            if (locked)
            {
                throw new IllegalStateException("ConfigData has been locked.");
            }
            super.setConfigDeployments(configDeployments);
        }
    }
}