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.Date; 021import java.util.HashMap; 022import java.util.HashSet; 023import java.util.Set; 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; 035import org.xml.sax.SAXException; 036 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; 042import org.ametys.web.service.ServiceExtensionPoint; 043 044/** 045 * This implementation of the services handler is based on services declared in the whole application 046 */ 047public class DefaultServicesAssignmentHandler extends AbstractLogEnabled implements ServicesAssignmentHandler, Serviceable, Initializable 048{ 049 private static final String __CACHE_ID = DefaultServicesAssignmentHandler.class.getName() + "$cache"; 050 051 /** The services manager */ 052 protected ServiceExtensionPoint _serviceEP; 053 /** The site type manager */ 054 protected SiteTypesExtensionPoint _siteTypeExtensionPoint; 055 /** The source resolver */ 056 protected SourceResolver _srcResolver; 057 /** The cache manager */ 058 protected AbstractCacheManager _cacheManager; 059 060 @Override 061 public void service(ServiceManager manager) throws ServiceException 062 { 063 _serviceEP = (ServiceExtensionPoint) manager.lookup(ServiceExtensionPoint.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_SERVICES_ASSIGNMENT_HANDLER_CACHE_LABEL"), 074 new I18nizableText("plugin.web", "PLUGINS_WEB_DEFAULT_SERVICES_ASSIGNMENT_HANDLER_CACHE_DESCRIPTION"), 075 true, 076 null); 077 } 078 079 @Override 080 public Set<String> getAvailableServices(SitemapElement page, String zoneName) 081 { 082 Site site = page.getSite(); 083 SiteType siteType = _siteTypeExtensionPoint.getExtension(site.getType()); 084 085 Set<String> services = _getServicesForZone(siteType.getName(), site.getSkinId(), page.getTemplate(), zoneName); 086 if (services != null) 087 { 088 services = new HashSet<>(services); 089 services.retainAll(_getPublicServices()); 090 return services; 091 } 092 093 services = _getServicesForTemplate(siteType.getName(), site.getSkinId(), page.getTemplate()); 094 if (services != null) 095 { 096 services = new HashSet<>(services); 097 services.retainAll(_getPublicServices()); 098 return services; 099 } 100 101 services = _getServicesForSkin(siteType.getName(), site.getSkinId()); 102 if (services != null) 103 { 104 services = new HashSet<>(services); 105 services.retainAll(_getPublicServices()); 106 return services; 107 } 108 109 services = _getServicesForSiteType(siteType.getName()); 110 if (services != null) 111 { 112 services = new HashSet<>(services); 113 services.retainAll(_getPublicServices()); 114 return services; 115 } 116 117 return _getPublicServices(); 118 } 119 120 public Set<String> limitAvailableServiceViews(Set<String> allViews, SitemapElement page, String zoneName, String serviceId) 121 { 122 Site site = page.getSite(); 123 SiteType siteType = _siteTypeExtensionPoint.getExtension(site.getType()); 124 125 ViewCacheValue services = _getServiceViewsForZone(siteType.getName(), site.getSkinId(), page.getTemplate(), zoneName, serviceId); 126 if (services != null) 127 { 128 return _viewCacheValueToViews(allViews, services); 129 } 130 131 services = _getServiceViewsForTemplate(siteType.getName(), site.getSkinId(), page.getTemplate(), serviceId); 132 if (services != null) 133 { 134 return _viewCacheValueToViews(allViews, services); 135 } 136 137 services = _getServiceViewsForSkin(siteType.getName(), site.getSkinId(), serviceId); 138 if (services != null) 139 { 140 return _viewCacheValueToViews(allViews, services); 141 } 142 143 services = _getServiceViewsForSiteType(siteType.getName(), serviceId); 144 if (services != null) 145 { 146 return _viewCacheValueToViews(allViews, services); 147 } 148 149 return allViews; 150 } 151 152 private Set<String> _getServicesForZone(String siteType, String skinName, String templateName, String zoneName) 153 { 154 String key = "services/" + siteType + "/" + skinName + "/" + templateName + "/" + zoneName; 155 String file = "skin:" + skinName + "://templates/" + templateName + "/conf/services-" + siteType + ".xml"; 156 157 Source configFile = null; 158 try 159 { 160 configFile = _srcResolver.resolveURI(file); 161 if (!configFile.exists()) 162 { 163 return null; 164 } 165 166 ServiceCache data = (ServiceCache) getCache().get(key); 167 if (data == null || !data.isValid(configFile.getLastModified())) 168 { 169 data = _parseZoneServices(configFile, zoneName); 170 getCache().put(key, data); 171 } 172 return data; 173 174 } 175 catch (IOException e) 176 { 177 getLogger().error("Unable to read the services configuration file", e); 178 return null; 179 } 180 finally 181 { 182 _srcResolver.release(configFile); 183 } 184 } 185 186 private ViewCacheValue _getServiceViewsForZone(String siteType, String skinName, String templateName, String zoneName, String serviceId) 187 { 188 String key = "views/" + siteType + "/" + skinName + "/" + templateName + "/" + zoneName; 189 String file = "skin:" + skinName + "://templates/" + templateName + "/conf/services-views-" + siteType + ".xml"; 190 191 Source configFile = null; 192 try 193 { 194 configFile = _srcResolver.resolveURI(file); 195 if (!configFile.exists()) 196 { 197 return null; 198 } 199 200 ViewCache data = (ViewCache) getCache().get(key); 201 if (data == null || !data.isValid(configFile.getLastModified())) 202 { 203 data = _parseZoneServicesViews(configFile, zoneName); 204 getCache().put(key, data); 205 } 206 return data != null ? data.get(serviceId) : null; 207 208 } 209 catch (IOException e) 210 { 211 getLogger().error("Unable to read the services views configuration file", e); 212 return null; 213 } 214 finally 215 { 216 _srcResolver.release(configFile); 217 } 218 } 219 220 private Set<String> _getServicesForTemplate (String siteType, String skinName, String templateName) 221 { 222 String key = "services/" + siteType + "/" + skinName + "/" + templateName; 223 String file = "skin:" + skinName + "://templates/" + templateName + "/conf/services-" + siteType + ".xml"; 224 225 Source configFile = null; 226 try 227 { 228 configFile = _srcResolver.resolveURI(file); 229 if (!configFile.exists()) 230 { 231 return null; 232 } 233 234 ServiceCache data = (ServiceCache) getCache().get(key); 235 if (data == null || !data.isValid(configFile.getLastModified())) 236 { 237 data = _parseTemplateServices(configFile); 238 getCache().put(key, data); 239 } 240 return data; 241 242 } 243 catch (IOException e) 244 { 245 getLogger().error("Unable to read the services configuration file", e); 246 return null; 247 } 248 finally 249 { 250 _srcResolver.release(configFile); 251 } 252 } 253 254 private ViewCacheValue _getServiceViewsForTemplate (String siteType, String skinName, String templateName, String serviceId) 255 { 256 String key = "views/" + siteType + "/" + skinName + "/" + templateName; 257 String file = "skin:" + skinName + "://templates/" + templateName + "/conf/services-views-" + siteType + ".xml"; 258 259 Source configFile = null; 260 try 261 { 262 configFile = _srcResolver.resolveURI(file); 263 if (!configFile.exists()) 264 { 265 return null; 266 } 267 268 ViewCache data = (ViewCache) getCache().get(key); 269 if (data == null || !data.isValid(configFile.getLastModified())) 270 { 271 data = _parseTemplateServicesViews(configFile); 272 getCache().put(key, data); 273 } 274 return data != null ? data.get(serviceId) : null; 275 276 } 277 catch (IOException e) 278 { 279 getLogger().error("Unable to read the services configuration file", e); 280 return null; 281 } 282 finally 283 { 284 _srcResolver.release(configFile); 285 } 286 } 287 288 private Set<String> _getServicesForSkin (String siteType, String skinName) 289 { 290 String key = "services/" + siteType + "/" + skinName; 291 String file = "skin:" + skinName + "://conf/services-" + siteType + ".xml"; 292 293 Source configFile = null; 294 try 295 { 296 configFile = _srcResolver.resolveURI(file); 297 if (!configFile.exists()) 298 { 299 return null; 300 } 301 302 ServiceCache data = (ServiceCache) getCache().get(key); 303 if (data == null || !data.isValid(configFile.getLastModified())) 304 { 305 data = _parseServices(configFile); 306 getCache().put(key, data); 307 } 308 return data; 309 310 } 311 catch (IOException e) 312 { 313 getLogger().error("Unable to read the services configuration file", e); 314 return null; 315 } 316 finally 317 { 318 _srcResolver.release(configFile); 319 } 320 } 321 322 private ViewCacheValue _getServiceViewsForSkin (String siteType, String skinName, String serviceId) 323 { 324 String key = "views/" + siteType + "/" + skinName; 325 String file = "skin:" + skinName + "://conf/services-views-" + siteType + ".xml"; 326 327 Source configFile = null; 328 try 329 { 330 configFile = _srcResolver.resolveURI(file); 331 if (!configFile.exists()) 332 { 333 return null; 334 } 335 336 ViewCache data = (ViewCache) getCache().get(key); 337 if (data == null || !data.isValid(configFile.getLastModified())) 338 { 339 data = _parseServicesViews(configFile); 340 getCache().put(key, data); 341 } 342 return data != null ? data.get(serviceId) : null; 343 344 } 345 catch (IOException e) 346 { 347 getLogger().error("Unable to read the services configuration file", e); 348 return null; 349 } 350 finally 351 { 352 _srcResolver.release(configFile); 353 } 354 } 355 356 private Set<String> _getServicesForSiteType (String siteType) 357 { 358 String key = "services/" + siteType; 359 String file = "context://WEB-INF/param/services-" + siteType + ".xml"; 360 361 Source configFile = null; 362 try 363 { 364 configFile = _srcResolver.resolveURI(file); 365 if (!configFile.exists()) 366 { 367 return null; 368 } 369 370 ServiceCache data = (ServiceCache) getCache().get(key); 371 if (data == null || !data.isValid(configFile.getLastModified())) 372 { 373 data = _parseServices(configFile); 374 getCache().put(key, data); 375 } 376 return data; 377 378 } 379 catch (IOException e) 380 { 381 getLogger().error("Unable to read the services configuration file", e); 382 return null; 383 } 384 finally 385 { 386 _srcResolver.release(configFile); 387 } 388 } 389 390 private ViewCacheValue _getServiceViewsForSiteType (String siteType, String serviceId) 391 { 392 String key = "views/" + siteType; 393 String file = "context://WEB-INF/param/services-views-" + siteType + ".xml"; 394 395 Source configFile = null; 396 try 397 { 398 configFile = _srcResolver.resolveURI(file); 399 if (!configFile.exists()) 400 { 401 return null; 402 } 403 404 ViewCache data = (ViewCache) getCache().get(key); 405 if (data == null || !data.isValid(configFile.getLastModified())) 406 { 407 data = _parseServicesViews(configFile); 408 getCache().put(key, data); 409 } 410 return data != null ? data.get(serviceId) : null; 411 412 } 413 catch (IOException e) 414 { 415 getLogger().error("Unable to read the services configuration file", e); 416 return null; 417 } 418 finally 419 { 420 _srcResolver.release(configFile); 421 } 422 } 423 424 /** 425 * Get the public services. 426 * @return the public services. 427 */ 428 protected ServiceCache _getPublicServices() 429 { 430 ServiceCache publicServices = new ServiceCache(new Date().getTime()); 431 432 for (String id : _serviceEP.getExtensionsIds()) 433 { 434 if (!_serviceEP.getExtension(id).isPrivate()) 435 { 436 publicServices.add(id); 437 } 438 } 439 440 return publicServices; 441 } 442 443 /** 444 * Parse a template configuration file. 445 * @param configFile the template configuration file. 446 * @return the set of available services for the template. 447 */ 448 protected ServiceCache _parseTemplateServices(Source configFile) 449 { 450 ServiceCache services = null; 451 452 if (!configFile.exists()) 453 { 454 return null; 455 } 456 457 try (InputStream is = configFile.getInputStream()) 458 { 459 Configuration configuration = new DefaultConfigurationBuilder().build(is); 460 461 Configuration tplConf = configuration.getChild("template", false); 462 if (tplConf != null) 463 { 464 services = _parseServices(tplConf, configFile.getLastModified()); 465 } 466 } 467 catch (IOException | ConfigurationException | SAXException e) 468 { 469 getLogger().error("Unable parse the services configuration file", e); 470 return null; 471 } 472 473 return services; 474 } 475 476 /** 477 * Parse a template configuration file. 478 * @param configFile the template configuration file. 479 * @return the set of available services views for the template. 480 */ 481 protected ViewCache _parseTemplateServicesViews(Source configFile) 482 { 483 ViewCache services = null; 484 485 if (!configFile.exists()) 486 { 487 return null; 488 } 489 490 try (InputStream is = configFile.getInputStream()) 491 { 492 Configuration configuration = new DefaultConfigurationBuilder().build(is); 493 494 Configuration tplConf = configuration.getChild("template", false); 495 if (tplConf != null) 496 { 497 services = _parseServicesViews(tplConf, configFile.getLastModified()); 498 } 499 } 500 catch (IOException | ConfigurationException | SAXException e) 501 { 502 getLogger().error("Unable to read the services views configuration file", e); 503 return null; 504 } 505 506 return services; 507 } 508 509 /** 510 * Parse a template configuration file and get the set of available services for a given zone. 511 * @param configFile the template configuration file. 512 * @param zoneName the zone name. 513 * @return the set of available services for the given zone. 514 */ 515 protected ServiceCache _parseZoneServices(Source configFile, String zoneName) 516 { 517 ServiceCache services = null; 518 519 if (!configFile.exists()) 520 { 521 return null; 522 } 523 524 try (InputStream is = configFile.getInputStream()) 525 { 526 Configuration configuration = new DefaultConfigurationBuilder().build(is); 527 528 Configuration zoneConfs = configuration.getChild("zones", false); 529 if (zoneConfs != null) 530 { 531 for (Configuration zoneConf : zoneConfs.getChildren("zone")) 532 { 533 if (zoneConf.getAttribute("id").equals(zoneName)) 534 { 535 services = _parseServices(zoneConf, configFile.getLastModified()); 536 } 537 } 538 } 539 } 540 catch (IOException | ConfigurationException | SAXException e) 541 { 542 getLogger().error("Unable parse the services configuration file", e); 543 return null; 544 } 545 546 return services; 547 } 548 549 /** 550 * Parse a template configuration file and get the set of available services for a given zone. 551 * @param configFile the template configuration file. 552 * @param zoneName the zone name. 553 * @return the set of available services views for the given zone. 554 */ 555 protected ViewCache _parseZoneServicesViews(Source configFile, String zoneName) 556 { 557 ViewCache services = null; 558 559 if (!configFile.exists()) 560 { 561 return null; 562 } 563 564 try (InputStream is = configFile.getInputStream()) 565 { 566 Configuration configuration = new DefaultConfigurationBuilder().build(is); 567 568 Configuration zoneConfs = configuration.getChild("zones", false); 569 if (zoneConfs != null) 570 { 571 for (Configuration zoneConf : zoneConfs.getChildren("zone")) 572 { 573 if (zoneConf.getAttribute("id").equals(zoneName)) 574 { 575 services = _parseServicesViews(zoneConf, configFile.getLastModified()); 576 } 577 } 578 } 579 } 580 catch (IOException | ConfigurationException | SAXException e) 581 { 582 getLogger().error("Unable parse the services views configuration file", e); 583 return null; 584 } 585 586 return services; 587 } 588 589 /** 590 * Parses the valid services for the site type 591 * @param configFile the configuration file. 592 * @return the services id in a Set 593 */ 594 protected ServiceCache _parseServices (Source configFile) 595 { 596 if (!configFile.exists()) 597 { 598 return null; 599 } 600 601 ServiceCache services = null; 602 603 try (InputStream is = configFile.getInputStream()) 604 { 605 Configuration configuration = new DefaultConfigurationBuilder().build(is); 606 607 services = _parseServices(configuration, configFile.getLastModified()); 608 } 609 catch (IOException | ConfigurationException | SAXException e) 610 { 611 getLogger().error("Unable parse the services configuration file", e); 612 return null; 613 } 614 615 return services; 616 } 617 618 /** 619 * Parses the valid services views for the site type 620 * @param configFile the configuration file. 621 * @return the services views id in a Set 622 */ 623 protected ViewCache _parseServicesViews(Source configFile) 624 { 625 if (!configFile.exists()) 626 { 627 return null; 628 } 629 630 ViewCache services = null; 631 632 try (InputStream is = configFile.getInputStream()) 633 { 634 Configuration configuration = new DefaultConfigurationBuilder().build(is); 635 636 services = _parseServicesViews(configuration, configFile.getLastModified()); 637 } 638 catch (IOException | ConfigurationException | SAXException e) 639 { 640 getLogger().error("Unable parse the services views configuration file", e); 641 return null; 642 } 643 644 return services; 645 } 646 647 /** 648 * Parses the valid services in a configuration. 649 * @param configuration the configuration. 650 * @param lastModificationDade last date of modification 651 * @return the services id in a Set 652 * @throws ConfigurationException if configuration is invalid 653 */ 654 protected ServiceCache _parseServices(Configuration configuration, long lastModificationDade) throws ConfigurationException 655 { 656 ServiceCache services; 657 658 String mode = configuration.getAttribute("mode", "include"); 659 if ("exclude".equals(mode)) 660 { 661 services = _getPublicServices(); 662 for (Configuration serviceConf : configuration.getChildren("service")) 663 { 664 String id = serviceConf.getAttribute("id"); 665 services.remove(id); 666 } 667 } 668 else 669 { 670 services = new ServiceCache(lastModificationDade); 671 for (Configuration serviceConf : configuration.getChildren("service")) 672 { 673 String id = serviceConf.getAttribute("id"); 674 services.add(id); 675 } 676 } 677 678 return services; 679 } 680 681 /** 682 * Parses the valid services in a configuration. 683 * @param configuration the configuration. 684 * @param lastModificationDade last date of modification 685 * @return the services id in a Set 686 * @throws ConfigurationException if configuration is invalid 687 */ 688 protected ViewCache _parseServicesViews(Configuration configuration, long lastModificationDade) throws ConfigurationException 689 { 690 ViewCache servicesViews = new ViewCache(lastModificationDade); 691 692 for (Configuration serviceConf : configuration.getChildren("service")) 693 { 694 Set<String> viewNames = new HashSet<>(); 695 696 String id = serviceConf.getAttribute("id"); 697 Configuration viewsConf = serviceConf.getChild("views"); 698 String mode = viewsConf.getAttribute("mode", "include"); 699 700 for (Configuration viewConf : viewsConf.getChildren("view")) 701 { 702 String viewName = viewConf.getAttribute("name") + ".xsl"; 703 viewNames.add(viewName); 704 } 705 706 servicesViews.put(id, new ViewCacheValue("exclude".equals(mode), viewNames)); 707 } 708 709 return servicesViews; 710 } 711 712 /** 713 * The cache is a HashSet of String + a date 714 */ 715 protected static class ServiceCache extends HashSet<String> 716 { 717 private long _sourceLastModified; 718 719 /** 720 * Build the cache 721 * @param sourceLastModified The last modification date 722 */ 723 public ServiceCache(long sourceLastModified) 724 { 725 super(); 726 } 727 728 /** 729 * Determine if the cache is valid 730 * @param newSourceLastModified The new last modification date 731 * @return true if the cache is still valid 732 */ 733 public boolean isValid(long newSourceLastModified) 734 { 735 return newSourceLastModified <= _sourceLastModified; 736 } 737 } 738 739 private Set<String> _viewCacheValueToViews(Set<String> allViews, ViewCacheValue viewCacheValue) 740 { 741 Set<String> views = new HashSet<>(allViews); 742 743 if (viewCacheValue.exclude) 744 { 745 views.removeAll(viewCacheValue.views); 746 } 747 else 748 { 749 views.retainAll(viewCacheValue.views); 750 } 751 752 return views; 753 } 754 755 private record ViewCacheValue(Boolean exclude, Set<String> views) { } 756 757 /** 758 * The cache is a HashMap of String + a date 759 */ 760 protected static class ViewCache extends HashMap<String, ViewCacheValue> 761 { 762 private long _sourceLastModified; 763 764 /** 765 * Build the cache 766 * @param sourceLastModified The last modification date 767 */ 768 public ViewCache(long sourceLastModified) 769 { 770 super(); 771 } 772 773 /** 774 * Determine if the cache is valid 775 * @param newSourceLastModified The new last modification date 776 * @return true if the cache is still valid 777 */ 778 public boolean isValid(long newSourceLastModified) 779 { 780 return newSourceLastModified <= _sourceLastModified; 781 } 782 } 783 784 private org.ametys.core.cache.Cache<String, Object> getCache() 785 { 786 return _cacheManager.get(__CACHE_ID); 787 } 788}