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.repository.page; 017 018import java.io.IOException; 019import java.io.InputStream; 020import java.util.HashSet; 021import java.util.Set; 022 023import org.apache.avalon.framework.activity.Initializable; 024import org.apache.avalon.framework.configuration.Configuration; 025import org.apache.avalon.framework.configuration.ConfigurationException; 026import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder; 027import org.apache.avalon.framework.logger.AbstractLogEnabled; 028import org.apache.avalon.framework.service.ServiceException; 029import org.apache.avalon.framework.service.ServiceManager; 030import org.apache.avalon.framework.service.Serviceable; 031import org.apache.excalibur.source.Source; 032import org.apache.excalibur.source.SourceResolver; 033import org.xml.sax.SAXException; 034 035import org.ametys.cms.contenttype.ContentType; 036import org.ametys.cms.contenttype.ContentTypeExtensionPoint; 037import org.ametys.core.cache.AbstractCacheManager; 038import org.ametys.runtime.i18n.I18nizableText; 039import org.ametys.web.repository.site.Site; 040import org.ametys.web.repository.site.SiteType; 041import org.ametys.web.repository.site.SiteTypesExtensionPoint; 042 043/** 044 * This implementation of the content types handler is based on content types declared in the whole application 045 */ 046public class DefaultContentTypesAssignmentHandler extends AbstractLogEnabled implements ContentTypesAssignmentHandler, Serviceable, Initializable 047{ 048 private static final String __CACHE_ID = DefaultContentTypesAssignmentHandler.class.getName() + "$cache"; 049 050 /** The content types manager */ 051 protected ContentTypeExtensionPoint _cTypeEP; 052 /** The site type manager */ 053 protected SiteTypesExtensionPoint _siteTypeExtensionPoint; 054 /** The source resolver */ 055 protected SourceResolver _srcResolver; 056 /** The cache manager */ 057 protected AbstractCacheManager _cacheManager; 058 059 060 @Override 061 public void service(ServiceManager manager) throws ServiceException 062 { 063 _cTypeEP = (ContentTypeExtensionPoint) manager.lookup(ContentTypeExtensionPoint.ROLE); 064 _siteTypeExtensionPoint = (SiteTypesExtensionPoint) manager.lookup(SiteTypesExtensionPoint.ROLE); 065 _srcResolver = (SourceResolver) manager.lookup(SourceResolver.ROLE); 066 _cacheManager = (AbstractCacheManager) manager.lookup(AbstractCacheManager.ROLE); 067 } 068 069 @Override 070 public void initialize() throws Exception 071 { 072 _cacheManager.createMemoryCache(__CACHE_ID, 073 new I18nizableText("plugin.web", "PLUGINS_WEB_DEFAULT_CONTENT_TYPE_ASSIGNMENT_HANDLER_CACHE_LABEL"), 074 new I18nizableText("plugin.web", "PLUGINS_WEB_DEFAULT_CONTENT_TYPE_ASSIGNMENT_HANDLER_CACHE_DESCRIPTION"), 075 true, 076 null); 077 } 078 079 @Override 080 public Set<String> getAvailableContentTypes(Site site) 081 { 082 return getAvailableContentTypes (site, false); 083 } 084 085 @Override 086 public Set<String> getAvailableContentTypes(Site site, boolean includePrivate) 087 { 088 Set<String> allCTypes = _getContentTypes(includePrivate, false, false, false); 089 090 SiteType siteType = _siteTypeExtensionPoint.getExtension(site.getType()); 091 092 Set<String> cTypes = _getContentTypesForSkin (siteType.getName(), site.getSkinId()); 093 if (cTypes != null) 094 { 095 cTypes.retainAll(allCTypes); 096 return cTypes; 097 } 098 099 cTypes = _getContentTypesForSiteType (siteType.getName()); 100 if (cTypes != null) 101 { 102 cTypes.retainAll(allCTypes); 103 return cTypes; 104 } 105 106 return allCTypes; 107 } 108 109 @Override 110 public Set<String> getAvailableContentTypes(Page page, String zoneName, boolean includePrivate) 111 { 112 Set<String> allCTypes = _getContentTypes(includePrivate, false, false, false); 113 114 Site site = page.getSite(); 115 SiteType siteType = _siteTypeExtensionPoint.getExtension(site.getType()); 116 117 Set<String> cTypes = _getContentTypesForZone(siteType.getName(), site.getSkinId(), page.getTemplate(), zoneName); 118 if (cTypes != null) 119 { 120 cTypes.retainAll(allCTypes); 121 return cTypes; 122 } 123 124 cTypes = _getContentTypesForTemplate(siteType.getName(), site.getSkinId(), page.getTemplate()); 125 if (cTypes != null) 126 { 127 cTypes.retainAll(allCTypes); 128 return cTypes; 129 } 130 131 cTypes = _getContentTypesForSkin (siteType.getName(), site.getSkinId()); 132 if (cTypes != null) 133 { 134 cTypes.retainAll(allCTypes); 135 return cTypes; 136 } 137 138 cTypes = _getContentTypesForSiteType (siteType.getName()); 139 if (cTypes != null) 140 { 141 cTypes.retainAll(allCTypes); 142 return cTypes; 143 } 144 145 return allCTypes; 146 } 147 148 @Override 149 public Set<String> getAvailableContentTypes(Page page, String zoneName) 150 { 151 return getAvailableContentTypes(page, zoneName, false); 152 } 153 154 private Set<String> _getContentTypesForZone(String siteType, String skinName, String templateName, String zoneName) 155 { 156 String key = siteType + "/" + skinName + "/" + templateName + "/" + zoneName; 157 String file = "skin:" + skinName + "://templates/" + templateName + "/conf/content-types-" + siteType + ".xml"; 158 159 Source configFile = null; 160 try 161 { 162 configFile = _srcResolver.resolveURI(file); 163 if (!configFile.exists()) 164 { 165 return null; 166 } 167 168 Cache data = getCache().get(key); 169 if (data == null || !data.isValid(configFile.getLastModified())) 170 { 171 data = _parseZoneContentTypes(configFile, zoneName); 172 getCache().put(key, data); 173 } 174 return data; 175 176 } 177 catch (IOException e) 178 { 179 getLogger().error("Unable to read the content types configuration file", e); 180 return null; 181 } 182 finally 183 { 184 _srcResolver.release(configFile); 185 } 186 } 187 188 private Set<String> _getContentTypesForTemplate (String siteType, String skinName, String templateName) 189 { 190 String key = siteType + "/" + skinName + "/" + templateName; 191 String file = "skin:" + skinName + "://templates/" + templateName + "/conf/content-types-" + siteType + ".xml"; 192 193 Source configFile = null; 194 try 195 { 196 configFile = _srcResolver.resolveURI(file); 197 if (!configFile.exists()) 198 { 199 return null; 200 } 201 202 Cache data = getCache().get(key); 203 if (data == null || !data.isValid(configFile.getLastModified())) 204 { 205 data = _parseTemplateContentTypes(configFile); 206 getCache().put(key, data); 207 } 208 return data; 209 210 } 211 catch (IOException e) 212 { 213 getLogger().error("Unable to read the content types configuration file", e); 214 return null; 215 } 216 finally 217 { 218 _srcResolver.release(configFile); 219 } 220 } 221 222 private Set<String> _getContentTypesForSkin (String siteType, String skinName) 223 { 224 String key = siteType + "/" + skinName; 225 String file = "skin:" + skinName + "://conf/content-types-" + siteType + ".xml"; 226 227 Source configFile = null; 228 try 229 { 230 configFile = _srcResolver.resolveURI(file); 231 if (!configFile.exists()) 232 { 233 return null; 234 } 235 236 Cache data = getCache().get(key); 237 if (data == null || !data.isValid(configFile.getLastModified())) 238 { 239 data = _parseContentTypes(configFile); 240 getCache().put(key, data); 241 } 242 return data; 243 244 } 245 catch (IOException e) 246 { 247 getLogger().error("Unable to read the content types configuration file", e); 248 return null; 249 } 250 finally 251 { 252 _srcResolver.release(configFile); 253 } 254 } 255 256 private Set<String> _getContentTypesForSiteType (String siteType) 257 { 258 String key = siteType; 259 String file = "context://WEB-INF/param/content-types-" + siteType + ".xml"; 260 261 Source configFile = null; 262 try 263 { 264 configFile = _srcResolver.resolveURI(file); 265 if (!configFile.exists()) 266 { 267 return null; 268 } 269 270 Cache data = getCache().get(key); 271 if (data == null || !data.isValid(configFile.getLastModified())) 272 { 273 data = _parseContentTypes(configFile); 274 getCache().put(key, data); 275 } 276 return data; 277 278 } 279 catch (IOException e) 280 { 281 getLogger().error("Unable to read the content types configuration file", e); 282 return null; 283 } 284 finally 285 { 286 _srcResolver.release(configFile); 287 } 288 } 289 290 /** 291 * Get the public content types. 292 * @param includePrivate <code>true</code> to include private content types 293 * @param includeReferenceTable <code>true</code> to include simple content types 294 * @param includeMixin <code>true</code> to include mixins 295 * @param includeAbstract <code>true</code> to include abstract content types 296 * @return the public content types. 297 */ 298 protected Set<String> _getContentTypes(boolean includePrivate, boolean includeReferenceTable, boolean includeMixin, boolean includeAbstract) 299 { 300 Set<String> publicTypes = new HashSet<>(); 301 302 for (String id : _cTypeEP.getExtensionsIds()) 303 { 304 ContentType cType = _cTypeEP.getExtension(id); 305 if ((includePrivate || !cType.isPrivate()) && (includeReferenceTable || !cType.isReferenceTable()) && (includeMixin || !cType.isMixin()) && (includeAbstract || !cType.isAbstract())) 306 { 307 publicTypes.add(id); 308 } 309 } 310 311 return publicTypes; 312 } 313 314 /** 315 * Parses the valid content types for a template 316 * @param configFile the template configuration file. 317 * @return the content types' id in a Set 318 */ 319 protected Cache _parseTemplateContentTypes(Source configFile) 320 { 321 Cache cTypes = null; 322 323 if (!configFile.exists()) 324 { 325 return null; 326 } 327 328 try (InputStream is = configFile.getInputStream()) 329 { 330 Configuration configuration = new DefaultConfigurationBuilder().build(is); 331 332 Configuration tplConf = configuration.getChild("template", false); 333 if (tplConf != null) 334 { 335 cTypes = _parseContentTypes(tplConf, configFile.getLastModified()); 336 } 337 } 338 catch (IOException e) 339 { 340 getLogger().error("Unable to read the content types configuration file", e); 341 return null; 342 } 343 catch (ConfigurationException e) 344 { 345 getLogger().error("Unable to parse the content types configuration file", e); 346 return null; 347 } 348 catch (SAXException e) 349 { 350 getLogger().error("Unable parse the content types configuration file", e); 351 return null; 352 } 353 354 return cTypes; 355 } 356 357 358 /** 359 * Parses the valid content types for the given zone. 360 * @param configFile the template configuration file. 361 * @param zoneName the zone name. 362 * @return the content types' id in a Set 363 * @throws IOException If an error occured while reading the file 364 */ 365 protected Cache _parseZoneContentTypes(Source configFile, String zoneName) throws IOException 366 { 367 Cache cTypes = null; 368 369 if (!configFile.exists()) 370 { 371 return null; 372 } 373 374 try (InputStream is = configFile.getInputStream()) 375 { 376 Configuration configuration = new DefaultConfigurationBuilder().build(is); 377 378 Configuration zoneConfs = configuration.getChild("zones", false); 379 if (zoneConfs != null) 380 { 381 for (Configuration zoneConf : zoneConfs.getChildren("zone")) 382 { 383 if (zoneConf.getAttribute("id").equals(zoneName)) 384 { 385 cTypes = _parseContentTypes(zoneConf, configFile.getLastModified()); 386 } 387 } 388 } 389 } 390 catch (ConfigurationException e) 391 { 392 getLogger().error("Unable to parse the content types configuration file", e); 393 return null; 394 } 395 catch (SAXException e) 396 { 397 getLogger().error("Unable parse the content types configuration file", e); 398 return null; 399 } 400 401 return cTypes; 402 } 403 404 /** 405 * Parses the valid content types for the site type 406 * @param configFile the configuration file. 407 * @return the content types' id in a Set 408 */ 409 protected Cache _parseContentTypes (Source configFile) 410 { 411 Cache cTypes = null; 412 413 if (!configFile.exists()) 414 { 415 return null; 416 } 417 418 try (InputStream is = configFile.getInputStream()) 419 { 420 Configuration configuration = new DefaultConfigurationBuilder().build(is); 421 422 cTypes = _parseContentTypes(configuration, configFile.getLastModified()); 423 } 424 catch (IOException e) 425 { 426 getLogger().error("Unable to read the content types configuration file", e); 427 return null; 428 } 429 catch (ConfigurationException e) 430 { 431 getLogger().error("Unable to parse the content types configuration file", e); 432 return null; 433 } 434 catch (SAXException e) 435 { 436 getLogger().error("Unable parse the content types configuration file", e); 437 return null; 438 } 439 440 return cTypes; 441 } 442 443 /** 444 * Parses the valid content types 445 * @param configuration the configuration. 446 * @param lastModificationDate the date the configuration was lastly modified 447 * @return the content types' id in a Set 448 * @throws ConfigurationException if configuration is invalid 449 */ 450 protected Cache _parseContentTypes(Configuration configuration, long lastModificationDate) throws ConfigurationException 451 { 452 Cache cTypes = new Cache(lastModificationDate); 453 454 String mode = configuration.getAttribute("mode", "include"); 455 if ("exclude".equals(mode)) 456 { 457 cTypes.addAll(_getContentTypes(false, false, false, false)); 458 for (Configuration cTypeConf : configuration.getChildren("content-type")) 459 { 460 String id = cTypeConf.getAttribute("id"); 461 cTypes.remove(id); 462 } 463 } 464 else 465 { 466 for (Configuration cTypeConf : configuration.getChildren("content-type")) 467 { 468 String id = cTypeConf.getAttribute("id"); 469 cTypes.add(id); 470 } 471 } 472 473 return cTypes; 474 } 475 476 /** 477 * The cache is a HashSet of String + a date 478 */ 479 protected static class Cache extends HashSet<String> 480 { 481 private long _sourceLastModified; 482 483 /** 484 * Build the cache 485 * @param sourceLastModified The last modification date 486 */ 487 public Cache(long sourceLastModified) 488 { 489 super(); 490 } 491 492 /** 493 * Determine if the cache is valid 494 * @param newSourceLastModified The new last modification date 495 * @return true if the cache is still valid 496 */ 497 public boolean isValid(long newSourceLastModified) 498 { 499 return newSourceLastModified <= _sourceLastModified; 500 } 501 } 502 503 private org.ametys.core.cache.Cache<String, Cache> getCache() 504 { 505 return _cacheManager.get(__CACHE_ID); 506 } 507 508} 509