001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (https://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software, please see the 018 * company website: https://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: https://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.ade.configuration; 029 030import org.opencms.ade.configuration.CmsADEConfigData.DetailInfo; 031import org.opencms.ade.configuration.CmsADEConfigDataInternal.ConfigReference; 032import org.opencms.ade.configuration.CmsADEConfigDataInternal.ConfigReferenceInstance; 033import org.opencms.ade.configuration.CmsADEConfigDataInternal.ConfigReferenceMeta; 034import org.opencms.ade.configuration.plugins.CmsSitePlugin; 035import org.opencms.ade.detailpage.CmsDetailPageInfo; 036import org.opencms.file.CmsObject; 037import org.opencms.file.CmsResource; 038import org.opencms.file.CmsResourceFilter; 039import org.opencms.file.types.CmsResourceTypeXmlContainerPage; 040import org.opencms.main.CmsException; 041import org.opencms.main.CmsLog; 042import org.opencms.main.OpenCms; 043import org.opencms.util.CmsStringUtil; 044import org.opencms.util.CmsUUID; 045 046import java.util.ArrayList; 047import java.util.Collections; 048import java.util.HashMap; 049import java.util.HashSet; 050import java.util.List; 051import java.util.Map; 052import java.util.Set; 053import java.util.concurrent.ExecutionException; 054import java.util.concurrent.TimeUnit; 055import java.util.stream.Collectors; 056 057import org.apache.commons.logging.Log; 058 059import com.google.common.base.Supplier; 060import com.google.common.base.Suppliers; 061import com.google.common.cache.Cache; 062import com.google.common.cache.CacheBuilder; 063import com.google.common.collect.HashMultimap; 064import com.google.common.collect.Lists; 065import com.google.common.collect.Maps; 066import com.google.common.collect.Multimaps; 067import com.google.common.collect.SetMultimap; 068 069/** 070 * An immutable object which represents the complete ADE configuration (sitemap and module configurations) 071 * at a certain instant in time.<p> 072 */ 073public class CmsADEConfigCacheState { 074 075 /** The logger instance for this class. */ 076 private static final Log LOG = CmsLog.getLog(CmsADEConfigCacheState.class); 077 078 /** The CMS context used for VFS operations. */ 079 private CmsObject m_cms; 080 081 /** Cached set of names of content types anywhere in the configuration. */ 082 private volatile Set<String> m_contentTypes; 083 084 /** Cache for detail page lists. */ 085 private Map<String, List<String>> m_detailPageCache; 086 087 /** Memoized supplier for the cached detail page ids. */ 088 private Supplier<Set<CmsUUID>> m_detailPageIdCache; 089 090 /** Cached detail page types. */ 091 private volatile Set<String> m_detailPageTypes; 092 093 /** The available element views. */ 094 private Map<CmsUUID, CmsElementView> m_elementViews; 095 096 /** The cached content types for folders. */ 097 private SetMultimap<String, String> m_folderTypes = HashMultimap.create(); 098 099 /** The merged configuration from all the modules. */ 100 private CmsADEConfigDataInternal m_moduleConfiguration; 101 102 /** The list of module configurations. */ 103 private List<CmsADEConfigDataInternal> m_moduleConfigurations; 104 105 /** The map of sitemap configurations by structure id. */ 106 private Map<CmsUUID, CmsADEConfigDataInternal> m_siteConfigurations = new HashMap<CmsUUID, CmsADEConfigDataInternal>(); 107 108 /** The configurations from the sitemap / VFS. */ 109 private Map<String, CmsADEConfigDataInternal> m_siteConfigurationsByPath = new HashMap<String, CmsADEConfigDataInternal>(); 110 111 /** The sitemap attribute editor configurations. */ 112 private Map<CmsUUID, CmsSitemapAttributeEditorConfiguration> m_sitemapAttributeEditorConfigurations; 113 114 /** Site plugins. */ 115 private Map<CmsUUID, CmsSitePlugin> m_sitePlugins; 116 117 /** Cache for attribute overview maps - keys are attribute names, values are maps from paths to attribute values. */ 118 private Cache<String, Map<String, String>> m_attributeValuesByPathCache = CacheBuilder.newBuilder().expireAfterWrite( 119 3, 120 TimeUnit.SECONDS).concurrencyLevel(2).build(); 121 122 /** Cached list of subsites to be included in the site selector. */ 123 private volatile List<String> m_subsitesForSiteSelector; 124 125 /** 126 * Creates a new configuration cache state.<p> 127 * 128 * @param cms the CMS context to use 129 * @param siteConfigurations the map of sitemap configuration beans by structure id 130 * @param moduleConfigs the complete list of module configurations 131 * @param elementViews the available element views 132 * @param sitePlugins the map of sitemap plugins 133 * @param attributeEditorConfigurations the attribute editor configurations 134 */ 135 public CmsADEConfigCacheState( 136 CmsObject cms, 137 Map<CmsUUID, CmsADEConfigDataInternal> siteConfigurations, 138 List<CmsADEConfigDataInternal> moduleConfigs, 139 Map<CmsUUID, CmsElementView> elementViews, 140 Map<CmsUUID, CmsSitePlugin> sitePlugins, 141 Map<CmsUUID, CmsSitemapAttributeEditorConfiguration> attributeEditorConfigurations) { 142 143 m_cms = cms; 144 m_siteConfigurations = siteConfigurations; 145 m_moduleConfigurations = moduleConfigs; 146 m_elementViews = elementViews; 147 m_sitePlugins = sitePlugins; 148 m_sitemapAttributeEditorConfigurations = attributeEditorConfigurations; 149 for (CmsADEConfigDataInternal data : siteConfigurations.values()) { 150 if (data.getBasePath() != null) { 151 m_siteConfigurationsByPath.put(data.getBasePath(), data); 152 } else { 153 LOG.info("Empty base path for sitemap configuration: " + data.getResource().getRootPath()); 154 } 155 } 156 m_moduleConfiguration = mergeConfigurations(moduleConfigs); 157 try { 158 m_folderTypes = computeFolderTypes(); 159 } catch (Exception e) { 160 m_folderTypes = HashMultimap.create(); 161 LOG.error(e.getLocalizedMessage(), e); 162 } 163 CacheBuilder<?, ?> detailPageCacheBuilder = CacheBuilder.newBuilder().concurrencyLevel(8).expireAfterWrite( 164 30, 165 TimeUnit.SECONDS); 166 m_detailPageCache = (Map<String, List<String>>)(detailPageCacheBuilder.build().asMap()); 167 m_detailPageIdCache = Suppliers.memoize(this::collectDetailPageIds); 168 169 } 170 171 /** 172 * Creates an empty ADE configuration cache state.<p> 173 * 174 * @param cms the CMS context 175 * @return the empty configuration cache state 176 */ 177 public static CmsADEConfigCacheState emptyState(CmsObject cms) { 178 179 return new CmsADEConfigCacheState( 180 cms, 181 Collections.<CmsUUID, CmsADEConfigDataInternal> emptyMap(), 182 Collections.<CmsADEConfigDataInternal> emptyList(), 183 Collections.<CmsUUID, CmsElementView> emptyMap(), 184 Collections.emptyMap(), 185 Collections.emptyMap()); 186 } 187 188 /** 189 * Computes the map from folder paths to content types for this ADE configuration state.<p> 190 * 191 * @return the map of content types by folder root paths 192 * 193 * @throws CmsException if something goes wrong 194 */ 195 public SetMultimap<String, String> computeFolderTypes() throws CmsException { 196 197 HashMultimap<String, String> folderTypes = HashMultimap.create(); 198 // do this first, since folder types from modules should be overwritten by folder types from sitemaps 199 if (m_moduleConfiguration != null) { 200 folderTypes.putAll(wrap(m_moduleConfiguration).getFolderTypes()); 201 } 202 203 List<CmsADEConfigDataInternal> configDataObjects = new ArrayList<CmsADEConfigDataInternal>( 204 m_siteConfigurationsByPath.values()); 205 for (CmsADEConfigDataInternal configData : configDataObjects) { 206 folderTypes.putAll(wrap(configData).getFolderTypes()); 207 } 208 return folderTypes; 209 } 210 211 /** 212 * Creates a new object which represents the changed configuration state given some updates, without 213 * changing the current configuration state (this object instance). 214 * 215 * @param sitemapUpdates a map containing changed sitemap configurations indexed by structure id (the map values are null if the corresponding sitemap configuration is not valid or could not be found) 216 * @param moduleUpdates the list of *all* module configurations, or null if no module configuration update is needed 217 * @param elementViewUpdates the updated element views, or null if no update needed 218 * @param sitePluginUpdates the new map of site plugins, or null if no update needed 219 * @param attributeEditorConfigurations the sitemap attribute editor configurations 220 * 221 * @return the new configuration state 222 */ 223 public CmsADEConfigCacheState createUpdatedCopy( 224 Map<CmsUUID, CmsADEConfigDataInternal> sitemapUpdates, 225 List<CmsADEConfigDataInternal> moduleUpdates, 226 Map<CmsUUID, CmsElementView> elementViewUpdates, 227 Map<CmsUUID, CmsSitePlugin> sitePluginUpdates, 228 Map<CmsUUID, CmsSitemapAttributeEditorConfiguration> attributeEditorConfigurations) { 229 230 Map<CmsUUID, CmsADEConfigDataInternal> newSitemapConfigs = Maps.newHashMap(m_siteConfigurations); 231 if (sitemapUpdates != null) { 232 for (Map.Entry<CmsUUID, CmsADEConfigDataInternal> entry : sitemapUpdates.entrySet()) { 233 CmsUUID key = entry.getKey(); 234 CmsADEConfigDataInternal value = entry.getValue(); 235 if (value != null) { 236 newSitemapConfigs.put(key, value); 237 } else { 238 newSitemapConfigs.remove(key); 239 } 240 } 241 } 242 List<CmsADEConfigDataInternal> newModuleConfigs = m_moduleConfigurations; 243 if (moduleUpdates != null) { 244 newModuleConfigs = moduleUpdates; 245 } 246 Map<CmsUUID, CmsElementView> newElementViews = m_elementViews; 247 if (elementViewUpdates != null) { 248 newElementViews = elementViewUpdates; 249 } 250 251 Map<CmsUUID, CmsSitePlugin> newSitePlugins = m_sitePlugins; 252 if (sitePluginUpdates != null) { 253 newSitePlugins = sitePluginUpdates; 254 } 255 256 Map<CmsUUID, CmsSitemapAttributeEditorConfiguration> newAttributeEditorConfigs = m_sitemapAttributeEditorConfigurations; 257 if (attributeEditorConfigurations != null) { 258 newAttributeEditorConfigs = attributeEditorConfigurations; 259 } 260 261 return new CmsADEConfigCacheState( 262 m_cms, 263 newSitemapConfigs, 264 newModuleConfigs, 265 newElementViews, 266 newSitePlugins, 267 newAttributeEditorConfigs); 268 } 269 270 /** 271 * Gets the sitemap attribute editor configuration with the given id (or null, if there isn't one). 272 * 273 * @param id the structure id of an attribute editor configuration 274 * @return the attribute editor configuration for the id 275 */ 276 public CmsSitemapAttributeEditorConfiguration getAttributeEditorConfiguration(CmsUUID id) { 277 278 return m_sitemapAttributeEditorConfigurations.get(id); 279 } 280 281 /** 282 * Gets a map of the values of a specific sitemap attribute for each subsite. 283 * 284 * <p>The keys of the map are the root paths of the subsite, and the values are the attribute values. 285 * 286 * @param attribute the attribute we want to get 287 * 288 * @return the map of attribute values 289 */ 290 public Map<String, String> getAttributeValuesByPath(String attribute) { 291 292 try { 293 return m_attributeValuesByPathCache.get(attribute, () -> { 294 Map<String, String> result = new HashMap<>(); 295 for (String path : m_siteConfigurationsByPath.keySet()) { 296 CmsADEConfigData config = lookupConfiguration(path); 297 String value = config.getAttribute(attribute, null); 298 if (value != null) { 299 result.put(path, value); 300 } 301 } 302 return Collections.unmodifiableMap(result); 303 }); 304 } catch (ExecutionException e) { 305 // shouldn't happen 306 LOG.error(e.getLocalizedMessage(), e); 307 return Collections.emptyMap(); 308 } 309 } 310 311 /** 312 * Gets the set of content types configured anywhere in sitemap configurations. 313 * 314 * @return the set of content types 315 */ 316 public Set<String> getContentTypes() { 317 318 if (m_contentTypes == null) { 319 Set<String> contentTypes = new HashSet<>(); 320 for (CmsADEConfigDataInternal config : m_siteConfigurations.values()) { 321 for (CmsResourceTypeConfig typeConfig : config.getOwnResourceTypes()) { 322 contentTypes.add(typeConfig.getTypeName()); 323 } 324 } 325 for (CmsResourceTypeConfig typeConfig : m_moduleConfiguration.getOwnResourceTypes()) { 326 contentTypes.add(typeConfig.getTypeName()); 327 } 328 m_contentTypes = Collections.unmodifiableSet(contentTypes); 329 } 330 return m_contentTypes; 331 } 332 333 /** 334 * Gets the detail page information for everything.<p> 335 * 336 * @param cms the current CMS context 337 * @return the list containing all detail information 338 */ 339 public List<DetailInfo> getDetailInfosForSubsites(CmsObject cms) { 340 341 List<DetailInfo> result = Lists.newArrayList(); 342 for (CmsADEConfigDataInternal configData : m_siteConfigurationsByPath.values()) { 343 List<DetailInfo> infosForSubsite = wrap(configData).getDetailInfos(cms); 344 result.addAll(infosForSubsite); 345 } 346 return result; 347 } 348 349 /** 350 * Gets the set of type names for which detail pages are configured in any sitemap configuration.<p> 351 * 352 * @return the set of type names with configured detail pages 353 */ 354 public Set<String> getDetailPageTypes() { 355 356 if (m_detailPageTypes != null) { 357 return m_detailPageTypes; 358 } 359 Set<String> result = new HashSet<String>(); 360 for (CmsADEConfigDataInternal configData : m_siteConfigurationsByPath.values()) { 361 List<CmsDetailPageInfo> detailPageInfos = configData.getOwnDetailPages(); 362 for (CmsDetailPageInfo info : detailPageInfos) { 363 result.add(info.getType()); 364 } 365 } 366 m_detailPageTypes = result; 367 return result; 368 } 369 370 /** 371 * Returns the element views.<p> 372 * 373 * @return the element views 374 */ 375 public Map<CmsUUID, CmsElementView> getElementViews() { 376 377 return Collections.unmodifiableMap(m_elementViews); 378 } 379 380 /** 381 * Gets the map of folder types.<p> 382 * 383 * @return the map of folder types 384 */ 385 public SetMultimap<String, String> getFolderTypes() { 386 387 return Multimaps.unmodifiableSetMultimap(m_folderTypes); 388 } 389 390 public Set<String> getParentFolderTypes(String rootPath) { 391 392 String parent = CmsResource.getParentFolder(rootPath); 393 if (parent == null) { 394 return Collections.emptySet(); 395 } 396 return getFolderTypes().get(parent); 397 398 } 399 400 /** 401 * Gets the raw detail page information, with no existence checks or path corrections. 402 * 403 * @return the detail page information 404 */ 405 public List<CmsDetailPageInfo> getRawDetailPages() { 406 407 List<CmsDetailPageInfo> result = new ArrayList<>(); 408 for (CmsADEConfigDataInternal config : m_siteConfigurationsByPath.values()) { 409 result.addAll(config.getOwnDetailPages()); 410 } 411 return result; 412 } 413 414 /** 415 * Returns the root paths to all configured sites and sub sites.<p> 416 * 417 * @return the root paths to all configured sites and sub sites 418 */ 419 public Set<String> getSiteConfigurationPaths() { 420 421 return Collections.unmodifiableSet(m_siteConfigurationsByPath.keySet()); 422 } 423 424 /** 425 * The map of site plugins, by structure id. 426 * 427 * @return the map of site plugins 428 */ 429 public Map<CmsUUID, CmsSitePlugin> getSitePlugins() { 430 431 return m_sitePlugins; 432 } 433 434 /** 435 * Gets subsites to be included in the site selector. 436 * 437 * @return the list of root paths of subsites that should be included in the site selector 438 */ 439 public List<String> getSubsitesForSiteSelector() { 440 441 if (m_subsitesForSiteSelector == null) { 442 List<String> paths = m_siteConfigurations.values().stream().filter( 443 conf -> conf.isIncludeInSiteSelector()).map(conf -> conf.getBasePath()).collect(Collectors.toList()); 444 m_subsitesForSiteSelector = Collections.unmodifiableList(paths); 445 } 446 return m_subsitesForSiteSelector; 447 } 448 449 /** 450 * Helper method to retrieve the parent folder type or <code>null</code> if none available.<p> 451 * 452 * @param rootPath the path of a resource 453 * @return the parent folder content type 454 */ 455 public boolean hasParentFolderType(String rootPath) { 456 457 String parent = CmsResource.getParentFolder(rootPath); 458 if (parent == null) { 459 return false; 460 } 461 return m_folderTypes.containsKey(parent); 462 } 463 464 /** 465 * Looks up the sitemap configuration for a root path.<p> 466 * @param rootPath the root path for which to look up the configuration 467 * 468 * @return the sitemap configuration for the given root path 469 */ 470 public CmsADEConfigData lookupConfiguration(String rootPath) { 471 472 CmsADEConfigDataInternal internalSiteConfig = getSiteConfigData(rootPath); 473 CmsADEConfigData result; 474 if (internalSiteConfig == null) { 475 result = wrap(m_moduleConfiguration); 476 } else { 477 result = wrap(internalSiteConfig); 478 } 479 return result; 480 } 481 482 /** 483 * Gets all detail page info beans which are defined anywhere in the configuration.<p> 484 * 485 * @return the list of detail page info beans 486 */ 487 protected List<CmsDetailPageInfo> getAllDetailPages() { 488 489 List<CmsDetailPageInfo> result = new ArrayList<CmsDetailPageInfo>(); 490 for (CmsADEConfigDataInternal configData : m_siteConfigurationsByPath.values()) { 491 result.addAll(wrap(configData).getAllDetailPages(true)); 492 } 493 return result; 494 } 495 496 /** 497 * Gets the CMS context used for VFS operations.<p> 498 * 499 * @return the CMS context used for VFS operations 500 */ 501 protected CmsObject getCms() { 502 503 return m_cms; 504 } 505 506 /** 507 * Gets all the detail pages for a given type.<p> 508 * 509 * @param type the name of the type 510 * 511 * @return the detail pages for that type 512 */ 513 protected List<String> getDetailPages(String type) { 514 515 List<String> result = m_detailPageCache.get(type); 516 if (result == null) { 517 result = new ArrayList<>(); 518 for (CmsADEConfigDataInternal configData : m_siteConfigurationsByPath.values()) { 519 for (CmsDetailPageInfo pageInfo : wrap(configData).getDetailPagesForType(type)) { 520 result.add(pageInfo.getUri()); 521 } 522 } 523 m_detailPageCache.put(type, result); 524 } 525 return Collections.unmodifiableList(result); 526 } 527 528 /** 529 * Gets the merged module configuration.<p> 530 * @return the merged module configuration instance 531 */ 532 protected CmsADEConfigData getModuleConfiguration() { 533 534 return wrap(m_moduleConfiguration); 535 } 536 537 /** 538 * Helper method for getting the best matching sitemap configuration object for a given root path, ignoring the module 539 * configuration.<p> 540 * 541 * For example, if there are configurations available for the paths /a, /a/b/c, /a/b/x and /a/b/c/d/e, then 542 * the method will return the configuration object for /a/b/c when passed the path /a/b/c/d. 543 * 544 * If no configuration data is found for the path, null will be returned.<p> 545 * 546 * @param path a root path 547 * @return the configuration data for the given path, or null if none was found 548 */ 549 protected CmsADEConfigDataInternal getSiteConfigData(String path) { 550 551 if (path == null) { 552 return null; 553 } 554 List<String> prefixes = getSiteConfigPaths(path); 555 if (prefixes.size() == 0) { 556 return null; 557 } 558 // for any two prefixes of a string, one is a prefix of the other. so the alphabetically last 559 // prefix is the longest prefix of all. 560 return m_siteConfigurationsByPath.get(prefixes.get(prefixes.size() - 1)); 561 } 562 563 /** 564 * Finds the paths of sitemap configuration base paths above a given path.<p> 565 * 566 * @param path the path for which to find the base paths of all valid sitemap configurations 567 * 568 * @return the list of base paths 569 */ 570 protected List<String> getSiteConfigPaths(String path) { 571 572 String normalizedPath = CmsStringUtil.joinPaths("/", path, "/"); 573 List<String> prefixes = new ArrayList<String>(); 574 575 List<String> parents = new ArrayList<String>(); 576 String currentPath = normalizedPath; 577 while (currentPath != null) { 578 parents.add(currentPath); 579 currentPath = CmsResource.getParentFolder(currentPath); 580 } 581 582 for (String parent : parents) { 583 if (m_siteConfigurationsByPath.containsKey(parent)) { 584 prefixes.add(parent); 585 } 586 } 587 Collections.sort(prefixes); 588 return prefixes; 589 } 590 591 /** 592 * Checks whether the given resource is configured as a detail page.<p> 593 * 594 * @param cms the current CMS context 595 * @param resource the resource to test 596 * 597 * @return true if the resource is configured as a detail page 598 */ 599 protected boolean isDetailPage(CmsObject cms, CmsResource resource) { 600 601 if (CmsResourceTypeXmlContainerPage.isContainerPage(resource)) { 602 Set<CmsUUID> detailPageIds = m_detailPageIdCache.get(); 603 if (detailPageIds.contains(resource.getStructureId())) { 604 // page may have been created/replaced after the detail page id cache was generated, 605 // so we don't just return false if it doesn't contain the id. 606 // instead we check the parent folder too in the next step. 607 return true; 608 } 609 610 try { 611 CmsResource parent = getCms().readResource( 612 CmsResource.getParentFolder(resource.getRootPath()), 613 CmsResourceFilter.ALL); 614 return detailPageIds.contains(parent.getStructureId()); 615 616 } catch (Exception e) { 617 LOG.info(e.getLocalizedMessage(), e); 618 return false; 619 } 620 } else if (resource.isFolder()) { 621 return m_detailPageIdCache.get().contains(resource.getStructureId()); 622 } else { 623 return false; 624 } 625 } 626 627 /** 628 * Merges a list of multiple configuration objects into a single configuration object.<p> 629 * 630 * @param configurations the list of configuration objects.<p> 631 * 632 * @return the merged configuration object 633 */ 634 protected CmsADEConfigDataInternal mergeConfigurations(List<CmsADEConfigDataInternal> configurations) { 635 636 if (configurations.isEmpty()) { 637 return new CmsADEConfigDataInternal(null); 638 } 639 for (int i = 0; i < (configurations.size() - 1); i++) { 640 configurations.get(i + 1).mergeParent(configurations.get(i)); 641 } 642 CmsADEConfigDataInternal result = configurations.get(configurations.size() - 1); 643 result.processModuleOrdering(); 644 return result; 645 } 646 647 /** 648 * Internal method for collecting structure ids of all configured detail pages and their parent folders. 649 * 650 * @return the structure ids of configured detail pages and their parent folders 651 */ 652 private Set<CmsUUID> collectDetailPageIds() { 653 654 List<CmsDetailPageInfo> allDetailPages = new ArrayList<CmsDetailPageInfo>(); 655 // First collect all detail page infos 656 for (CmsADEConfigDataInternal configData : m_siteConfigurationsByPath.values()) { 657 List<CmsDetailPageInfo> detailPageInfos = configData.getOwnDetailPages(); 658 allDetailPages.addAll(detailPageInfos); 659 } 660 Set<CmsUUID> detailPageOrDetailPageFolderIds = new HashSet<>(); 661 for (CmsDetailPageInfo detailPageInfo : allDetailPages) { 662 try { 663 CmsResource detailPageRes = getCms().readResource(detailPageInfo.getId(), CmsResourceFilter.ALL); 664 detailPageOrDetailPageFolderIds.add(detailPageInfo.getId()); 665 if (detailPageRes.isFile()) { 666 CmsResource parent = getCms().readParentFolder(detailPageInfo.getId()); 667 detailPageOrDetailPageFolderIds.add(parent.getStructureId()); 668 } else { 669 CmsResource defaultfile = getCms().readDefaultFile("" + detailPageInfo.getId()); 670 if (defaultfile != null) { 671 detailPageOrDetailPageFolderIds.add(defaultfile.getStructureId()); 672 } 673 } 674 } catch (Exception e) { 675 LOG.info(e.getLocalizedMessage(), e); 676 } 677 } 678 return Collections.unmodifiableSet(detailPageOrDetailPageFolderIds); 679 } 680 681 /** 682 * For a given master configuration, lists all directly and indirectly referenced master configurations, in sitemap config inheritance order (i.e. referenced master configurations preceding the 683 * configurations from which they are referenced). 684 * 685 * @param result the list to append the results to 686 * @param current the configuration reference to start with 687 * @param seen the set of structure ids of sitemap configurations already visited 688 */ 689 private void fillMasterConfigurations( 690 List<ConfigReferenceInstance> result, 691 ConfigReferenceInstance current, 692 Set<CmsUUID> seen) { 693 694 CmsUUID currentId = current.getConfig().getResource().getStructureId(); 695 if (seen.contains(currentId)) { 696 LOG.warn("Loop in sitemap configuration references, target = " + current.getConfig().getBasePath()); 697 return; 698 } 699 seen.add(currentId); 700 // Recursively add the referenced master configurations before adding the current configuration in the end 701 for (ConfigReference configRef : current.getConfig().getMasterConfigs()) { 702 CmsADEConfigDataInternal config = m_siteConfigurations.get(configRef.getId()); 703 if (config != null) { 704 ConfigReferenceMeta combinedMeta = current.getMeta().combine(configRef.getMeta()); 705 ConfigReferenceInstance combinedRef = new ConfigReferenceInstance(config, combinedMeta); 706 fillMasterConfigurations(result, combinedRef, seen); 707 } else { 708 LOG.warn( 709 "Master configuration with id " 710 + configRef.getId() 711 + " not found, referenced by " 712 + current.getConfig().getResource().getRootPath()); 713 } 714 } 715 result.add(current); 716 seen.remove(currentId); 717 } 718 719 /** 720 * Wraps the internal config data into a bean which manages the lookup of inherited configurations.<p> 721 * 722 * @param data the config data to wrap 723 * 724 * @return the wrapper object 725 */ 726 private CmsADEConfigData wrap(CmsADEConfigDataInternal data) { 727 728 String path = data.getBasePath(); 729 List<ConfigReferenceInstance> configList = Lists.newArrayList(); 730 configList.add(new ConfigReferenceInstance(m_moduleConfiguration)); 731 if (path != null) { 732 List<String> siteConfigPaths = getSiteConfigPaths(path); 733 for (String siteConfigPath : siteConfigPaths) { 734 CmsADEConfigDataInternal currentConfig = m_siteConfigurationsByPath.get(siteConfigPath); 735 fillMasterConfigurations(configList, new ConfigReferenceInstance(currentConfig), new HashSet<>()); 736 } 737 } 738 CmsADEConfigData result = new CmsADEConfigData(data, this, new CmsADEConfigurationSequence(configList)); 739 List<I_CmsSitemapExtraInfo> extraInfo = new ArrayList<>(); 740 for (I_CmsSitemapExtraInfoProvider provider : OpenCms.getADEManager().getSitemapExtraInfoProviders()) { 741 extraInfo.add(provider.getExtraInfo(m_cms)); 742 } 743 result.m_extraInfo = extraInfo; 744 return result; 745 } 746}