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.AbstractCacheManager.CacheType; 038import org.ametys.core.cache.Cache; 039import org.ametys.plugins.core.impl.cache.AbstractCacheKey; 040import org.ametys.runtime.i18n.I18nizableText; 041import org.ametys.web.repository.page.Page; 042import org.ametys.web.repository.site.Site; 043 044/** 045 * This implementation of the templates handler is based uppon a configuration. 046 */ 047public class StaticTemplatesAssignmentHandler extends AbstractLogEnabled implements TemplatesAssignmentHandler, Serviceable, Initializable 048{ 049 private static final String __TEMPLATE_CACHE = StaticTemplatesAssignmentHandler.class.getName() + "$tplCache"; 050 private static final String __LAST_CONF_UPDATE = StaticTemplatesAssignmentHandler.class.getName() + "$lastConfUpdate"; 051 052 /** The skins manager */ 053 protected SkinsManager _skinsManager; 054 /** The source resolver */ 055 protected SourceResolver _srcResolver; 056 057 private AbstractCacheManager _cacheManager; 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 } 066 067 @Override 068 public void initialize() throws Exception 069 { 070 _createCaches(); 071 } 072 073 /** 074 * Creates the caches 075 */ 076 protected void _createCaches() 077 { 078 _cacheManager.createCache(__TEMPLATE_CACHE, 079 new I18nizableText("plugin.web", "PLUGINS_WEB_TPL_CACHE_LABEL"), 080 new I18nizableText("plugin.web", "PLUGINS_WEB_TPL_CACHE_DESCRIPTION"), 081 CacheType.MEMORY, 082 true); 083 084 _cacheManager.createCache(__LAST_CONF_UPDATE, 085 new I18nizableText("plugin.web", "PLUGINS_WEB_LAST_CONF_UPDATE_CACHE_LABEL"), 086 new I18nizableText("plugin.web", "PLUGINS_WEB_LAST_CONF_UPDATE_CACHE_DESCRIPTION"), 087 CacheType.MEMORY, 088 true); 089 } 090 091 @Override 092 public Set<String> getAvailablesTemplates(String skinName) 093 { 094 Skin skin = _skinsManager.getSkin(skinName); 095 if (skin == null) 096 { 097 getLogger().warn("The unexisting skin '" + skinName + "' is referenced (by a site?)"); 098 return Collections.emptySet(); 099 } 100 101 _refreshValues(skin); 102 103 return _getTemplatesForSkinNameFromCache(skinName); 104 } 105 106 @Override 107 public Set<String> getAvailablesTemplates(Page page) 108 { 109 Set<String> availableTemplates = new HashSet<>(); 110 111 Site site = page.getSite(); 112 113 String skinName = site.getSkinId(); 114 Skin skin = _skinsManager.getSkin(skinName); 115 if (skin == null) 116 { 117 getLogger().warn("The site '" + site.getName() + "' reference the unexisting skin '" + site.getSkinId() + "'"); 118 return availableTemplates; 119 } 120 121 _refreshValues (skin); 122 Set<String> skinTemplates = _getTemplatesForSkinNameFromCache(skinName); 123 Cache<TemplateKey, AssignmentCondition> templateCache = _getTemplateCache(); 124 for (String templateName : skinTemplates) 125 { 126 AssignmentCondition assignmentCondition = templateCache.get(TemplateKey.of(skinName, templateName)); 127 if (assignmentCondition.matchCondition(page)) 128 { 129 availableTemplates.add(templateName); 130 } 131 } 132 133 return availableTemplates; 134 } 135 136 /** 137 * Get the available templates for assignment 138 * @param skin The skin 139 */ 140 protected void _refreshValues(Skin skin) 141 { 142 String skinId = skin.getId(); 143 Source source = null; 144 try 145 { 146 source = _srcResolver.resolveURI("skin:" + skinId + "://conf/template_assignment.xml"); 147 if (source.exists()) 148 { 149 Cache<String, Long> lastConfUpdateCache = _getLastConfUpdateCache(); 150 if (!lastConfUpdateCache.hasKey(skinId) || lastConfUpdateCache.get(skinId) < source.getLastModified()) 151 { 152 lastConfUpdateCache.put(skinId, source.getLastModified()); 153 154 try (InputStream is = source.getInputStream()) 155 { 156 Configuration configuration = new DefaultConfigurationBuilder().build(is); 157 _parseAvailableTemplates (skin, configuration); 158 } 159 } 160 } 161 else 162 { 163 _getAllTemplatesWithoutCondition(skin); 164 } 165 } 166 catch (Exception e) 167 { 168 getLogger().error("Unable to read the available templates configuration file", e); 169 } 170 } 171 172 private void _parseAvailableTemplates (Skin skin, Configuration configuration) throws ConfigurationException 173 { 174 String skinId = skin.getId(); 175 boolean exclude = configuration.getChild("list", false) == null || "exclude".equals(configuration.getChild("list").getAttribute("mode", "include")); 176 177 if (exclude) 178 { 179 _getAllTemplatesWithoutCondition (skin); 180 } 181 182 Configuration[] tplList = configuration.getChild("list").getChildren("template"); 183 for (Configuration tplConf : tplList) 184 { 185 String reName = tplConf.getAttribute("name"); 186 Pattern namePattern = _getPattern (reName); 187 188 for (String templateName : skin.getTemplates()) 189 { 190 if (namePattern.matcher(templateName).matches()) 191 { 192 Cache<TemplateKey, AssignmentCondition> templateCache = _getTemplateCache(); 193 TemplateKey templateKey = TemplateKey.of(skinId, templateName); 194 if (!exclude) 195 { 196 templateCache.put(templateKey, new AssignmentCondition(skinId, templateName)); 197 } 198 else 199 { 200 templateCache.invalidate(templateKey); 201 } 202 } 203 } 204 } 205 206 Set<String> findCondition = new HashSet<>(); 207 208 // Conditions 209 Configuration[] conditions = configuration.getChild("conditions").getChildren("condition"); 210 for (Configuration conditionConf : conditions) 211 { 212 String reName = conditionConf.getAttribute("template"); 213 Pattern namePattern = _getPattern (reName); 214 215 Set<String> skinTemplates = _getTemplatesForSkinNameFromCache(skinId); 216 for (String templateName : skinTemplates) 217 { 218 if (!findCondition.contains(templateName) && namePattern.matcher(templateName).matches()) 219 { 220 findCondition.add(templateName); 221 222 AssignmentCondition condition = _getTemplateCache().get(TemplateKey.of(skinId, templateName)); 223 224 String regexp = conditionConf.getChild("page").getAttribute("regexp_path", null); 225 if (regexp != null) 226 { 227 condition.setRegExpPath(regexp, false); 228 } 229 else 230 { 231 String reverseRegexp = conditionConf.getChild("page").getAttribute("reverse_regexp_path", null); 232 if (reverseRegexp != null) 233 { 234 condition.setRegExpPath(reverseRegexp, true); 235 } 236 } 237 } 238 } 239 } 240 } 241 242 private void _getAllTemplatesWithoutCondition (Skin skin) 243 { 244 String skinId = skin.getId(); 245 Cache<TemplateKey, AssignmentCondition> templateCache = _getTemplateCache(); 246 for (String templateName : skin.getTemplates()) 247 { 248 templateCache.put(TemplateKey.of(skinId, templateName), new AssignmentCondition(skinId, templateName)); 249 } 250 } 251 252 private Pattern _getPattern (String pattern) 253 { 254 String regexp = "^" + pattern.replaceAll("\\*", "([^/]*)") + "$"; 255 return Pattern.compile(regexp); 256 } 257 258 259 private Cache<TemplateKey, AssignmentCondition> _getTemplateCache() 260 { 261 return _cacheManager.get(__TEMPLATE_CACHE); 262 } 263 264 private Cache<String, Long> _getLastConfUpdateCache() 265 { 266 return _cacheManager.get(__LAST_CONF_UPDATE); 267 } 268 269 private Set<String> _getTemplatesForSkinNameFromCache(String skinName) 270 { 271 return _getTemplateCache() 272 .asMap() 273 .keySet() 274 .stream() 275 .filter(key -> 276 { 277 String currentSkinName = key.getSkinName(); 278 return currentSkinName.equals(skinName); 279 }) 280 .map(TemplateKey::getTemplateName) 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}