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.context.Context;
030import org.apache.avalon.framework.context.ContextException;
031import org.apache.avalon.framework.context.Contextualizable;
032import org.apache.avalon.framework.service.ServiceException;
033import org.apache.avalon.framework.service.ServiceManager;
034import org.apache.avalon.framework.service.Serviceable;
035import org.apache.excalibur.source.Source;
036import org.apache.excalibur.source.SourceResolver;
037
038import org.ametys.core.cache.AbstractCacheManager;
039import org.ametys.core.cache.Cache;
040import org.ametys.core.util.filereloader.FileReloader;
041import org.ametys.core.util.filereloader.FileReloaderUtils;
042import org.ametys.runtime.i18n.I18nizableText;
043import org.ametys.runtime.plugin.component.AbstractLogEnabled;
044import org.ametys.web.skin.SkinsManager;
045
046/**
047 * Implementation of {@link SitemapIconsProvider} for icons provided by skins
048 */
049public class SkinSitemapIconsProvider extends AbstractLogEnabled implements SitemapIconsProvider, Initializable, Serviceable, FileReloader, Contextualizable
050{
051    private static final Pattern __SKIN_SOURCE_PATTERN = Pattern.compile("^skin:(.*)://(.*)$");
052    private static final String __ICON_CACHE = SkinSitemapIconsProvider.class.getName() + "$iconCache";
053    
054    /** The skins manager */
055    protected SkinsManager _skinsManager;
056    /** The source resolver */
057    protected SourceResolver _srcResolver;
058
059    private AbstractCacheManager _cacheManager;
060    private FileReloaderUtils _fileReloader;
061    private ServiceManager _smanager;
062    private Context _context;
063
064    @Override
065    public void service(ServiceManager smanager) throws ServiceException
066    {
067        _smanager = smanager;
068        _skinsManager = (SkinsManager) smanager.lookup(SkinsManager.ROLE);
069        _srcResolver = (SourceResolver) smanager.lookup(SourceResolver.ROLE);
070        _cacheManager = (AbstractCacheManager) smanager.lookup(AbstractCacheManager.ROLE);
071        _fileReloader = (FileReloaderUtils) smanager.lookup(FileReloaderUtils.ROLE);
072    }
073    
074    public void contextualize(Context context) throws ContextException
075    {
076        _context = context;
077    }
078    
079    @Override
080    public void initialize() throws Exception
081    {
082        _createCaches();
083    }
084
085    /**
086     * Creates the caches
087     */
088    protected void _createCaches()
089    {
090        if (!_cacheManager.hasCache(__ICON_CACHE))
091        {
092            _cacheManager.createMemoryCache(__ICON_CACHE, 
093                    new I18nizableText("plugin.web", "PLUGINS_WEB_SKIN_SITEMAP_ICONS_CACHE_LABEL"),
094                    new I18nizableText("plugin.web", "PLUGINS_WEB_SKIN_SITEMAP_ICONS_CACHE_DESCRIPTION"),
095                    true,
096                    null);
097        }
098    }
099    
100    @Override
101    public Set<SitemapTreeIndicator> getIcons(String skinName)
102    {
103        try
104        {
105            boolean force = !_getIconCache().hasKey(skinName);
106            _fileReloader.updateFile("skin:" + skinName + "://conf/sitemap-icons.xml", force, this);
107        }
108        catch (Exception e)
109        {
110            getLogger().error("Unable to read the available sitemap icons configuration file for skin '{}'", skinName, e);
111        }
112
113        Set<SitemapTreeIndicator> icons = _getIconCache().get(skinName);
114        return icons != null ? icons : Collections.emptySet();
115    }
116
117    public void updateFile(String sourceUrl, Source source, InputStream is) throws Exception
118    {
119        Matcher m = __SKIN_SOURCE_PATTERN.matcher(sourceUrl);
120        if (!m.matches())
121        {
122            throw new MalformedURLException("URI must be like skin:<name>://path/to/resource. Location was '" + sourceUrl + "'");
123        }
124        
125        String skinId = m.group(1);
126        Set<SitemapTreeIndicator> icons = new LinkedHashSet<>();
127        
128        if (is != null)
129        {
130            int index = 0;
131            Configuration configuration = new DefaultConfigurationBuilder().build(is);
132            for (Configuration iconConfiguration : configuration.getChildren("icon"))
133            {
134                DefaultSitemapIndicator indicator = DefaultSitemapIndicator.newInstance(skinId + "." + index++, iconConfiguration, _smanager, _context, "skin." + skinId, "/skin/" + skinId + "/resources/");
135                icons.add(indicator);
136            }
137        }
138        
139        // Fill cache
140        Cache<String, Set<SitemapTreeIndicator>> iconCache = _getIconCache();
141        iconCache.put(skinId, icons);
142    }
143    
144    public String getId(String sourceUrl)
145    {
146        return sourceUrl;
147    }
148    
149    private Cache<String, Set<SitemapTreeIndicator>> _getIconCache()
150    {
151        return _cacheManager.get(__ICON_CACHE);
152    }
153}