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