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.HashSet; 021import java.util.Set; 022import java.util.regex.Pattern; 023import java.util.stream.Collectors; 024 025import org.apache.avalon.framework.activity.Initializable; 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.core.cache.AbstractCacheManager; 037import org.ametys.core.cache.Cache; 038import org.ametys.plugins.core.impl.cache.AbstractCacheKey; 039import org.ametys.runtime.i18n.I18nizableText; 040import org.ametys.web.repository.page.Page; 041import org.ametys.web.repository.site.Site; 042 043/** 044 * This implementation of the templates handler is based uppon a configuration. 045 */ 046public class StaticTemplatesAssignmentHandler extends AbstractLogEnabled implements TemplatesAssignmentHandler, Serviceable, Initializable 047{ 048 private static final String __TEMPLATE_CACHE = StaticTemplatesAssignmentHandler.class.getName() + "$tplCache"; 049 private static final String __LAST_CONF_UPDATE = StaticTemplatesAssignmentHandler.class.getName() + "$lastConfUpdate"; 050 051 /** The skins manager */ 052 protected SkinsManager _skinsManager; 053 /** The source resolver */ 054 protected SourceResolver _srcResolver; 055 056 private AbstractCacheManager _cacheManager; 057 058 @Override 059 public void service(ServiceManager smanager) throws ServiceException 060 { 061 _skinsManager = (SkinsManager) smanager.lookup(SkinsManager.ROLE); 062 _srcResolver = (SourceResolver) smanager.lookup(SourceResolver.ROLE); 063 _cacheManager = (AbstractCacheManager) smanager.lookup(AbstractCacheManager.ROLE); 064 } 065 066 @Override 067 public void initialize() throws Exception 068 { 069 _createCaches(); 070 } 071 072 /** 073 * Creates the caches 074 */ 075 protected void _createCaches() 076 { 077 _cacheManager.createMemoryCache(__TEMPLATE_CACHE, 078 new I18nizableText("plugin.web", "PLUGINS_WEB_TPL_CACHE_LABEL"), 079 new I18nizableText("plugin.web", "PLUGINS_WEB_TPL_CACHE_DESCRIPTION"), 080 true, 081 null); 082 083 _cacheManager.createMemoryCache(__LAST_CONF_UPDATE, 084 new I18nizableText("plugin.web", "PLUGINS_WEB_LAST_CONF_UPDATE_CACHE_LABEL"), 085 new I18nizableText("plugin.web", "PLUGINS_WEB_LAST_CONF_UPDATE_CACHE_DESCRIPTION"), 086 true, 087 null); 088 } 089 090 @Override 091 public Set<String> getAvailablesTemplates(String skinName) 092 { 093 Skin skin = _skinsManager.getSkin(skinName); 094 if (skin == null) 095 { 096 getLogger().warn("The unexisting skin '" + skinName + "' is referenced (by a site?)"); 097 return Collections.emptySet(); 098 } 099 100 _refreshValues(skin); 101 102 return _getTemplatesForSkinNameFromCache(skinName); 103 } 104 105 @Override 106 public Set<String> getAvailablesTemplates(Page page) 107 { 108 Set<String> availableTemplates = new HashSet<>(); 109 110 Site site = page.getSite(); 111 112 String skinName = site.getSkinId(); 113 Skin skin = _skinsManager.getSkin(skinName); 114 if (skin == null) 115 { 116 getLogger().warn("The site '" + site.getName() + "' reference the unexisting skin '" + site.getSkinId() + "'"); 117 return availableTemplates; 118 } 119 120 _refreshValues (skin); 121 Set<String> skinTemplates = _getTemplatesForSkinNameFromCache(skinName); 122 Cache<TemplateKey, AssignmentCondition> templateCache = _getTemplateCache(); 123 for (String templateName : skinTemplates) 124 { 125 AssignmentCondition assignmentCondition = templateCache.get(TemplateKey.of(skinName, templateName)); 126 if (assignmentCondition.matchCondition(page)) 127 { 128 availableTemplates.add(templateName); 129 } 130 } 131 132 return availableTemplates; 133 } 134 135 /** 136 * Get the available templates for assignment 137 * @param skin The skin 138 */ 139 protected void _refreshValues(Skin skin) 140 { 141 String skinId = skin.getId(); 142 Source source = null; 143 try 144 { 145 source = _srcResolver.resolveURI("skin:" + skinId + "://conf/template_assignment.xml"); 146 if (source.exists()) 147 { 148 Cache<String, Long> lastConfUpdateCache = _getLastConfUpdateCache(); 149 if (!lastConfUpdateCache.hasKey(skinId) || lastConfUpdateCache.get(skinId) < source.getLastModified()) 150 { 151 lastConfUpdateCache.put(skinId, source.getLastModified()); 152 153 try (InputStream is = source.getInputStream()) 154 { 155 Configuration configuration = new DefaultConfigurationBuilder().build(is); 156 _parseAvailableTemplates (skin, configuration); 157 } 158 } 159 } 160 else 161 { 162 _getAllTemplatesWithoutCondition(skin); 163 } 164 } 165 catch (Exception e) 166 { 167 getLogger().error("Unable to read the available templates configuration file", e); 168 } 169 } 170 171 private void _parseAvailableTemplates (Skin skin, Configuration configuration) throws ConfigurationException 172 { 173 String skinId = skin.getId(); 174 boolean exclude = configuration.getChild("list", false) == null || "exclude".equals(configuration.getChild("list").getAttribute("mode", "include")); 175 176 if (exclude) 177 { 178 _getAllTemplatesWithoutCondition (skin); 179 } 180 181 Configuration[] tplList = configuration.getChild("list").getChildren("template"); 182 for (Configuration tplConf : tplList) 183 { 184 String reName = tplConf.getAttribute("name"); 185 Pattern namePattern = _getPattern (reName); 186 187 for (String templateName : skin.getTemplates()) 188 { 189 if (namePattern.matcher(templateName).matches()) 190 { 191 Cache<TemplateKey, AssignmentCondition> templateCache = _getTemplateCache(); 192 TemplateKey templateKey = TemplateKey.of(skinId, templateName); 193 if (!exclude) 194 { 195 templateCache.put(templateKey, new AssignmentCondition(skinId, templateName)); 196 } 197 else 198 { 199 templateCache.invalidate(templateKey); 200 } 201 } 202 } 203 } 204 205 Set<String> findCondition = new HashSet<>(); 206 207 // Conditions 208 Configuration[] conditions = configuration.getChild("conditions").getChildren("condition"); 209 for (Configuration conditionConf : conditions) 210 { 211 String reName = conditionConf.getAttribute("template"); 212 Pattern namePattern = _getPattern (reName); 213 214 Set<String> skinTemplates = _getTemplatesForSkinNameFromCache(skinId); 215 for (String templateName : skinTemplates) 216 { 217 if (!findCondition.contains(templateName) && namePattern.matcher(templateName).matches()) 218 { 219 findCondition.add(templateName); 220 221 AssignmentCondition condition = _getTemplateCache().get(TemplateKey.of(skinId, templateName)); 222 223 String regexp = conditionConf.getChild("page").getAttribute("regexp_path", null); 224 if (regexp != null) 225 { 226 condition.setRegExpPath(regexp, false); 227 } 228 else 229 { 230 String reverseRegexp = conditionConf.getChild("page").getAttribute("reverse_regexp_path", null); 231 if (reverseRegexp != null) 232 { 233 condition.setRegExpPath(reverseRegexp, true); 234 } 235 } 236 } 237 } 238 } 239 } 240 241 private void _getAllTemplatesWithoutCondition (Skin skin) 242 { 243 String skinId = skin.getId(); 244 Cache<TemplateKey, AssignmentCondition> templateCache = _getTemplateCache(); 245 for (String templateName : skin.getTemplates()) 246 { 247 templateCache.put(TemplateKey.of(skinId, templateName), new AssignmentCondition(skinId, templateName)); 248 } 249 } 250 251 private Pattern _getPattern (String pattern) 252 { 253 String regexp = "^" + pattern.replaceAll("\\*", "([^/]*)") + "$"; 254 return Pattern.compile(regexp); 255 } 256 257 258 private Cache<TemplateKey, AssignmentCondition> _getTemplateCache() 259 { 260 return _cacheManager.get(__TEMPLATE_CACHE); 261 } 262 263 private Cache<String, Long> _getLastConfUpdateCache() 264 { 265 return _cacheManager.get(__LAST_CONF_UPDATE); 266 } 267 268 private Set<String> _getTemplatesForSkinNameFromCache(String skinName) 269 { 270 return _getTemplateCache() 271 .asMap() 272 .keySet() 273 .stream() 274 .filter(key -> 275 { 276 String currentSkinName = key.getSkinName(); 277 return currentSkinName.equals(skinName); 278 }) 279 .map(TemplateKey::getTemplateName) 280 .filter(s -> !s.equals("sitemap")) // The sitemap template is a special template for the SitemapPageTool 281 .collect(Collectors.toSet()); 282 } 283 284 /** 285 * Class representing the condition for a template assignment 286 */ 287 public static class AssignmentCondition 288 { 289 private String _skinName; 290 private String _templateName; 291 private String _rePath; 292 private boolean _reverse; 293 294 /** 295 * Constructor 296 * @param skinName the skin name 297 * @param templateName the template name 298 */ 299 public AssignmentCondition (String skinName, String templateName) 300 { 301 _skinName = skinName; 302 _templateName = templateName; 303 } 304 305 /** 306 * Set the RegExp for path 307 * @param rePath the RegExp for path 308 * @param reverse true to reverse mode 309 */ 310 public void setRegExpPath (String rePath, boolean reverse) 311 { 312 _rePath = (rePath.startsWith("^") ? "" : "^") + rePath + (rePath.startsWith("$") ? "" : "$"); 313 _reverse = reverse; 314 } 315 316 /** 317 * Test if page matches condition 318 * @param page the page to test 319 * @return true if page matches condition 320 */ 321 public boolean matchCondition (Page page) 322 { 323 if (_rePath == null) 324 { 325 return true; 326 } 327 328 if (!_reverse) 329 { 330 return Pattern.compile(_rePath).matcher(page.getPathInSitemap()).matches(); 331 } 332 else 333 { 334 return !Pattern.compile(_rePath).matcher(page.getPathInSitemap()).matches(); 335 } 336 } 337 338 /** 339 * Get the skin name 340 * @return the skin name 341 */ 342 public String getSkinName () 343 { 344 return _skinName; 345 } 346 347 /** 348 * Get the template name 349 * @return the template name 350 */ 351 public String getTemplateName () 352 { 353 return _templateName; 354 } 355 } 356 357 private static final class TemplateKey extends AbstractCacheKey 358 { 359 private TemplateKey(String skinName, String templateName) 360 { 361 super(skinName, templateName); 362 } 363 364 static TemplateKey of(String skinName, String templateName) 365 { 366 return new TemplateKey(skinName, templateName); 367 } 368 369 String getSkinName() 370 { 371 return (String) getFields().get(0); 372 } 373 374 String getTemplateName() 375 { 376 return (String) getFields().get(1); 377 } 378 } 379 380}