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