001/*
002 *  Copyright 2010 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.skin;
017
018import java.io.InputStream;
019import java.util.Collections;
020import java.util.HashMap;
021import java.util.HashSet;
022import java.util.Map;
023import java.util.Set;
024import java.util.regex.Pattern;
025
026import org.apache.avalon.framework.configuration.Configuration;
027import org.apache.avalon.framework.configuration.ConfigurationException;
028import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder;
029import org.apache.avalon.framework.logger.AbstractLogEnabled;
030import org.apache.avalon.framework.service.ServiceException;
031import org.apache.avalon.framework.service.ServiceManager;
032import org.apache.avalon.framework.service.Serviceable;
033import org.apache.excalibur.source.Source;
034import org.apache.excalibur.source.SourceResolver;
035
036import org.ametys.web.repository.page.Page;
037import org.ametys.web.repository.site.Site;
038
039/**
040 * This implementation of the templates handler is based uppon a configuration. 
041 */
042public class StaticTemplatesAssignmentHandler extends AbstractLogEnabled implements TemplatesAssignmentHandler, Serviceable
043{
044    /** The skins manager */
045    protected SkinsManager _skinsManager;
046    /** The source resolver */
047    protected SourceResolver _srcResolver;
048    
049    private Map<String, Map<String, AssignmentCondition>> _tplCache = new HashMap<>();
050    private Map<String, Long> _lastConfUpdate = new HashMap<>();
051    
052    @Override
053    public void service(ServiceManager smanager) throws ServiceException
054    {
055        _skinsManager = (SkinsManager) smanager.lookup(SkinsManager.ROLE);
056        _srcResolver = (SourceResolver) smanager.lookup(SourceResolver.ROLE);
057    }
058    
059    @Override
060    public Set<String> getAvailablesTemplates(String skinName)
061    {
062        Skin skin = _skinsManager.getSkin(skinName);
063        if (skin == null)
064        {
065            getLogger().warn("The unexisting skin '" + skinName + "' is referenced (by a site?)");
066            return Collections.emptySet();
067        }
068
069        _refreshValues(skin);
070        
071        return _tplCache.get(skinName).keySet();
072    }
073    
074    @Override
075    public Set<String> getAvailablesTemplates(Page page)
076    {
077        Set<String> availableTemplates = new HashSet<>();
078        
079        Site site = page.getSite();
080        
081        String skinName = site.getSkinId();
082        Skin skin = _skinsManager.getSkin(skinName);
083        if (skin == null)
084        {
085            getLogger().warn("The site '" + site.getName() + "' reference the unexisting skin '" + site.getSkinId() + "'");
086            return availableTemplates;
087        }
088        
089        _refreshValues (skin);
090        
091        Map<String, AssignmentCondition> templates = _tplCache.get(skinName);
092        for (String templateName : templates.keySet())
093        {
094            if (templates.get(templateName).matchCondition(page))
095            {
096                availableTemplates.add(templateName);
097            }
098        }
099        
100        return availableTemplates;
101    }
102    
103    /**
104     * Get the available templates for assignment
105     * @param skin The skin
106     */
107    protected void _refreshValues(Skin skin)
108    {
109        Source source = null;
110        try
111        {
112            source = _srcResolver.resolveURI("context://skins/" + skin.getId() + "/conf/template_assignment.xml");
113            if (source.exists())
114            {
115                if (!_lastConfUpdate.containsKey(skin.getId()) || _lastConfUpdate.get(skin.getId()) < source.getLastModified())
116                {
117                    _lastConfUpdate.put(skin.getId(), source.getLastModified());
118                
119                    try (InputStream is = source.getInputStream())
120                    {
121                        Configuration configuration = new DefaultConfigurationBuilder().build(is);
122                        _parseAvailableTemplates (skin, configuration);
123                    }
124                }
125            }
126            else
127            {
128                _getAllTemplatesWithoutCondition(skin);
129            }
130        }
131        catch (Exception e)
132        {
133            getLogger().error("Unable to read the available templates configuration file", e);
134        }
135    }
136    
137    private void _parseAvailableTemplates (Skin skin, Configuration configuration) throws ConfigurationException
138    {
139        boolean exclude = configuration.getChild("list", false) == null || "exclude".equals(configuration.getChild("list").getAttribute("mode", "include"));
140        
141        if (exclude)
142        {
143            _getAllTemplatesWithoutCondition (skin);          
144        }
145        
146        Configuration[] tplList = configuration.getChild("list").getChildren("template");
147        for (Configuration tplConf : tplList)
148        {
149            String reName = tplConf.getAttribute("name");
150            Pattern namePattern = _getPattern (reName);
151            
152            for (String templateName : skin.getTemplates())
153            {
154                if (namePattern.matcher(templateName).matches())
155                {
156                    if (!exclude)
157                    {
158                        if (!_tplCache.containsKey(skin.getId()))
159                        {
160                            _tplCache.put(skin.getId(), new HashMap<String, AssignmentCondition>());
161                        }
162                        Map<String, AssignmentCondition> m = _tplCache.get(skin.getId());
163                        m.put(templateName, new AssignmentCondition(skin.getId(), templateName));
164                    }
165                    else
166                    {
167                        Map<String, AssignmentCondition> m = _tplCache.get(skin.getId());
168                        m.remove(templateName);
169                    }
170                }
171            }
172        }
173        
174        Set<String> findCondition = new HashSet<>();
175        
176        // Conditions
177        Configuration[] conditions = configuration.getChild("conditions").getChildren("condition");
178        for (Configuration conditionConf : conditions)
179        {
180            String reName = conditionConf.getAttribute("template");
181            Pattern namePattern = _getPattern (reName);
182            
183            Set<String> skinTemplates = _tplCache.get(skin.getId()).keySet();
184            for (String templateName : skinTemplates)
185            {
186                if (!findCondition.contains(templateName) && namePattern.matcher(templateName).matches())
187                {
188                    findCondition.add(templateName);
189
190                    AssignmentCondition condition = _tplCache.get(skin.getId()).get(templateName);
191                    
192                    String regexp = conditionConf.getChild("page").getAttribute("regexp_path", null);
193                    if (regexp != null)
194                    {
195                        condition.setRegExpPath(regexp, false);
196                    }
197                    else
198                    {
199                        String reverseRegexp = conditionConf.getChild("page").getAttribute("reverse_regexp_path", null);
200                        if (reverseRegexp != null)
201                        {
202                            condition.setRegExpPath(reverseRegexp, true);
203                        }
204                    }
205                }
206            }
207        }
208    }
209    
210    private void _getAllTemplatesWithoutCondition (Skin skin)
211    {
212        Map<String, AssignmentCondition> m = new HashMap<>();
213        _tplCache.put(skin.getId(), m);
214        
215        for (String templateName : skin.getTemplates())
216        {
217            m.put(templateName, new AssignmentCondition(skin.getId(), templateName));
218        }
219    }
220    
221    private Pattern _getPattern (String pattern)
222    {
223        String regexp = "^" + pattern.replaceAll("\\*", "([^/]*)") + "$";
224        return Pattern.compile(regexp);
225    }
226    
227    /**
228     * Class representing the condition for a template assignment
229     */
230    public class AssignmentCondition 
231    {
232        private String _skinName;
233        private String _templateName;
234        private String _rePath;
235        private boolean _reverse;
236        
237        /**
238         * Constructor
239         * @param skinName the skin name
240         * @param templateName the template name
241         */
242        public AssignmentCondition (String skinName, String templateName)
243        {
244            _skinName = skinName;
245            _templateName = templateName;
246        }
247        
248        /**
249         * Set the RegExp for path
250         * @param rePath the RegExp for path
251         * @param reverse true to reverse mode
252         */
253        public void setRegExpPath (String rePath, boolean reverse)
254        {
255            _rePath = (rePath.startsWith("^") ? "" : "^") + rePath + (rePath.startsWith("$") ? "" : "$");
256            _reverse = reverse;
257        }
258        
259        /**
260         * Test if page matches condition
261         * @param page the page to test
262         * @return true if page matches condition
263         */
264        public boolean matchCondition (Page page)
265        {
266            if (_rePath == null)
267            {
268                return true;
269            }
270            
271            if (!_reverse)
272            {
273                return Pattern.compile(_rePath).matcher(page.getPathInSitemap()).matches();
274            }
275            else
276            {
277                return !Pattern.compile(_rePath).matcher(page.getPathInSitemap()).matches();
278            }
279        }
280        
281        /**
282         * Get the skin name
283         * @return the skin name
284         */
285        public String getSkinName ()
286        {
287            return _skinName;
288        }
289        
290        /**
291         * Get the template name
292         * @return the template name
293         */
294        public String getTemplateName ()
295        {
296            return _templateName;
297        }
298    }
299    
300}