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