001/*
002 *  Copyright 2021 Anyware Services
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package org.ametys.web.sitemap;
017
018import java.io.InputStream;
019import java.net.MalformedURLException;
020import java.util.Collections;
021import java.util.LinkedHashSet;
022import java.util.Set;
023import java.util.regex.Matcher;
024import java.util.regex.Pattern;
025
026import org.apache.avalon.framework.activity.Initializable;
027import org.apache.avalon.framework.configuration.Configuration;
028import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder;
029import org.apache.avalon.framework.service.ServiceException;
030import org.apache.avalon.framework.service.ServiceManager;
031import org.apache.avalon.framework.service.Serviceable;
032import org.apache.excalibur.source.Source;
033import org.apache.excalibur.source.SourceResolver;
034
035import org.ametys.core.cache.AbstractCacheManager;
036import org.ametys.core.cache.Cache;
037import org.ametys.core.util.filereloader.FileReloader;
038import org.ametys.core.util.filereloader.FileReloaderUtils;
039import org.ametys.runtime.i18n.I18nizableText;
040import org.ametys.runtime.plugin.component.AbstractLogEnabled;
041import org.ametys.web.skin.SkinsManager;
042
043/**
044 * Implementation of {@link SitemapIcons} for icons provided by skins
045 */
046public class SkinSitemapIcons extends AbstractLogEnabled implements SitemapIcons, Initializable, Serviceable, FileReloader
047{
048    private static final Pattern __SKIN_SOURCE_PATTERN = Pattern.compile("^skin:(.*)://(.*)$");
049    private static final String __ICON_CACHE = SkinSitemapIcons.class.getName() + "$iconCache";
050    
051    /** The skins manager */
052    protected SkinsManager _skinsManager;
053    /** The source resolver */
054    protected SourceResolver _srcResolver;
055
056    private AbstractCacheManager _cacheManager;
057    private FileReloaderUtils _fileReloader;
058
059    @Override
060    public void service(ServiceManager smanager) throws ServiceException
061    {
062        _skinsManager = (SkinsManager) smanager.lookup(SkinsManager.ROLE);
063        _srcResolver = (SourceResolver) smanager.lookup(SourceResolver.ROLE);
064        _cacheManager = (AbstractCacheManager) smanager.lookup(AbstractCacheManager.ROLE);
065        _fileReloader = (FileReloaderUtils) smanager.lookup(FileReloaderUtils.ROLE);
066    }
067    
068    @Override
069    public void initialize() throws Exception
070    {
071        _createCaches();
072    }
073
074    /**
075     * Creates the caches
076     */
077    protected void _createCaches()
078    {
079        if (!_cacheManager.hasCache(__ICON_CACHE))
080        {
081            _cacheManager.createMemoryCache(__ICON_CACHE, 
082                    new I18nizableText("plugin.web", "PLUGINS_WEB_SITEMAP_ICONS_CACHE_LABEL"),
083                    new I18nizableText("plugin.web", "PLUGINS_WEB_SITEMAP_ICONS_CACHE_DESCRIPTION"),
084                    true,
085                    null);
086        }
087    }
088    
089    @Override
090    public Set<SitemapIcon> getIcons(String skinName)
091    {
092        try
093        {
094            boolean force = !_getIconCache().hasKey(skinName);
095            _fileReloader.updateFile("skin:" + skinName + "://conf/sitemap-icons.xml", force, this);
096        }
097        catch (Exception e)
098        {
099            getLogger().error("Unable to read the available sitemap icons configuration file for skin '{}'", e);
100        }
101
102        Set<SitemapIcon> icons = _getIconCache().get(skinName);
103        return icons != null ? icons : Collections.emptySet();
104    }
105
106    public void updateFile(String sourceUrl, Source source, InputStream is) throws Exception
107    {
108        Matcher m = __SKIN_SOURCE_PATTERN.matcher(sourceUrl);
109        if (!m.matches())
110        {
111            throw new MalformedURLException("URI must be like skin:<name>://path/to/resource. Location was '" + sourceUrl + "'");
112        }
113        
114        String skinId = m.group(1);
115        Set<SitemapIcon> icons = new LinkedHashSet<>();
116        
117        if (is != null)
118        {
119            Configuration configuration = new DefaultConfigurationBuilder().build(is);
120            for (Configuration iconConfiguration : configuration.getChildren("icon"))
121            {
122                SitemapIcon icon = new SitemapIcon();
123                icon.configure(iconConfiguration, "skin/" + skinId + "/resources/");
124                icons.add(icon);
125            }
126        }
127        
128        // Fill cache
129        Cache<String, Set<SitemapIcon>> iconCache = _getIconCache();
130        iconCache.put(skinId, icons);
131    }
132    
133    public String getId(String sourceUrl)
134    {
135        return sourceUrl;
136    }
137    
138    private Cache<String, Set<SitemapIcon>> _getIconCache()
139    {
140        return _cacheManager.get(__ICON_CACHE);
141    }
142}