001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (C) Alkacon Software (http://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: http://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: http://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.CmsADEConfigDataInternal.AttributeValue; 031import org.opencms.ade.configuration.CmsADEConfigDataInternal.ConfigReferenceMeta; 032import org.opencms.ade.configuration.formatters.CmsFormatterBeanParser; 033import org.opencms.ade.configuration.formatters.CmsFormatterChangeSet; 034import org.opencms.ade.configuration.formatters.CmsFormatterConfigurationCacheState; 035import org.opencms.ade.configuration.formatters.CmsFormatterIndex; 036import org.opencms.ade.configuration.plugins.CmsSitePlugin; 037import org.opencms.ade.containerpage.shared.CmsContainer; 038import org.opencms.ade.containerpage.shared.CmsContainerElement; 039import org.opencms.ade.containerpage.shared.CmsFormatterConfig; 040import org.opencms.ade.detailpage.CmsDetailPageInfo; 041import org.opencms.ade.galleries.CmsAddContentRestriction; 042import org.opencms.file.CmsObject; 043import org.opencms.file.CmsResource; 044import org.opencms.file.CmsResourceFilter; 045import org.opencms.file.types.CmsResourceTypeFolder; 046import org.opencms.file.types.CmsResourceTypeFunctionConfig; 047import org.opencms.file.types.CmsResourceTypeXmlContent; 048import org.opencms.file.types.I_CmsResourceType; 049import org.opencms.gwt.CmsIconUtil; 050import org.opencms.gwt.shared.CmsGwtConstants; 051import org.opencms.jsp.util.CmsFunctionRenderer; 052import org.opencms.loader.CmsLoaderException; 053import org.opencms.main.CmsException; 054import org.opencms.main.CmsLog; 055import org.opencms.main.OpenCms; 056import org.opencms.main.OpenCmsServlet; 057import org.opencms.util.CmsStringUtil; 058import org.opencms.util.CmsUUID; 059import org.opencms.workplace.editors.directedit.CmsAdvancedDirectEditProvider.SitemapDirectEditPermissions; 060import org.opencms.xml.CmsXmlContentDefinition; 061import org.opencms.xml.containerpage.CmsFormatterConfiguration; 062import org.opencms.xml.containerpage.CmsXmlDynamicFunctionHandler; 063import org.opencms.xml.containerpage.I_CmsFormatterBean; 064import org.opencms.xml.content.CmsXmlContentFactory; 065import org.opencms.xml.content.CmsXmlContentProperty; 066 067import java.util.ArrayList; 068import java.util.Arrays; 069import java.util.Collection; 070import java.util.Collections; 071import java.util.HashMap; 072import java.util.HashSet; 073import java.util.Iterator; 074import java.util.LinkedHashMap; 075import java.util.List; 076import java.util.Map; 077import java.util.Objects; 078import java.util.Set; 079import java.util.concurrent.ExecutionException; 080import java.util.stream.Collectors; 081 082import org.apache.commons.lang3.builder.ReflectionToStringBuilder; 083import org.apache.commons.logging.Log; 084 085import com.google.common.base.Optional; 086import com.google.common.cache.CacheBuilder; 087import com.google.common.cache.CacheLoader; 088import com.google.common.cache.LoadingCache; 089import com.google.common.collect.ArrayListMultimap; 090import com.google.common.collect.ImmutableList; 091import com.google.common.collect.Lists; 092import com.google.common.collect.Maps; 093import com.google.common.collect.Multimap; 094import com.google.common.collect.Sets; 095 096/** 097 * A class which represents the accessible configuration data at a given point in a sitemap.<p> 098 */ 099public class CmsADEConfigData { 100 101 /** 102 * Bean which contains the detail information for a single sub-sitemap and resource type.<p> 103 * 104 * This includes both information about the detail page itself, as well as the path of the folder 105 * which is used to store that content type in this subsitemap.<p> 106 * 107 */ 108 public class DetailInfo { 109 110 /** The base path of the sitemap configuration where this information originates from. */ 111 private String m_basePath; 112 113 /** The information about the detail page info itself. */ 114 private CmsDetailPageInfo m_detailPageInfo; 115 116 /** The content folder path. */ 117 private String m_folderPath; 118 119 /** The detail type. */ 120 private String m_type; 121 122 /** 123 * Creates a new instance.<p> 124 * 125 * @param folderPath the content folder path 126 * @param detailPageInfo the detail page information 127 * @param type the detail type 128 * @param basePath the base path of the sitemap configuration 129 */ 130 public DetailInfo(String folderPath, CmsDetailPageInfo detailPageInfo, String type, String basePath) { 131 132 m_folderPath = folderPath; 133 m_detailPageInfo = detailPageInfo; 134 m_type = type; 135 m_basePath = basePath; 136 137 } 138 139 /** 140 * Gets the base path of the sitemap configuration from which this information is coming.<p> 141 * 142 * @return the base path 143 */ 144 public String getBasePath() { 145 146 return m_basePath; 147 } 148 149 /** 150 * Gets the detail page information.<p> 151 * 152 * @return the detail page information 153 */ 154 public CmsDetailPageInfo getDetailPageInfo() { 155 156 return m_detailPageInfo; 157 } 158 159 /** 160 * Gets the content folder path.<p> 161 * 162 * @return the content folder path 163 */ 164 public String getFolderPath() { 165 166 return m_folderPath; 167 } 168 169 /** 170 * Gets the detail type.<p> 171 * 172 * @return the detail type 173 */ 174 public String getType() { 175 176 return m_type; 177 } 178 179 /** 180 * Sets the base path.<p> 181 * 182 * @param basePath the new base path value 183 */ 184 public void setBasePath(String basePath) { 185 186 m_basePath = basePath; 187 } 188 189 /** 190 * @see java.lang.Object#toString() 191 */ 192 @Override 193 public String toString() { 194 195 return ReflectionToStringBuilder.toString(this); 196 } 197 } 198 199 /** Sitemap attribute for the upload folder. */ 200 public static final String ATTR_BINARY_UPLOAD_TARGET = "binary.upload.target"; 201 202 /** Prefix for logging special request log messages. */ 203 public static final String REQ_LOG_PREFIX = "[CmsADEConfigData] "; 204 205 /** Channel for logging special request log messages. */ 206 public static final String REQUEST_LOG_CHANNEL = "org.opencms.ade.configuration.CmsADEConfigData.request"; 207 208 /** The log instance for this class. */ 209 private static final Log LOG = CmsLog.getLog(CmsADEConfigData.class); 210 211 /** Prefixes for internal settings which might be passed as formatter keys to findFormatter(). */ 212 private static final HashSet<String> systemSettingPrefixes = new HashSet<>( 213 Arrays.asList("element", "model", "source", "use", "cms", "is")); 214 215 /** The wrapped configuration bean containing the actual data. */ 216 protected CmsADEConfigDataInternal m_data; 217 218 /** Lazily initialized map of formatters. */ 219 private Map<CmsUUID, I_CmsFormatterBean> m_activeFormatters; 220 221 /** Lazily initialized cache for active formatters by formatter key. */ 222 private Multimap<String, I_CmsFormatterBean> m_activeFormattersByKey; 223 224 /** The sitemap attributes (may be null if not yet computed). */ 225 private Map<String, AttributeValue> m_attributes; 226 227 /** The cache state to which the wrapped configuration bean belongs. */ 228 private CmsADEConfigCacheState m_cache; 229 230 /** Current formatter configuration. */ 231 private CmsFormatterConfigurationCacheState m_cachedFormatters; 232 233 /** The configuration sequence (contains the list of all sitemap configuration data beans to be used for inheritance). */ 234 private CmsADEConfigurationSequence m_configSequence; 235 236 /** Cache for formatters by container type. */ 237 private Map<String, List<I_CmsFormatterBean>> m_formattersByContainerType = new HashMap<>(); 238 239 /** Cache for formatters by display type. */ 240 private Map<String, List<I_CmsFormatterBean>> m_formattersByDisplayType = new HashMap<>(); 241 242 /** Lazily initialized cache for formatters by JSP id. */ 243 private Multimap<CmsUUID, I_CmsFormatterBean> m_formattersByJspId; 244 245 /** Lazily initialized cache for formatters by formatter key. */ 246 private Multimap<String, I_CmsFormatterBean> m_formattersByKey; 247 248 /** Loading cache for for formatters grouped by type. */ 249 private LoadingCache<String, List<I_CmsFormatterBean>> m_formattersByTypeCache = CacheBuilder.newBuilder().build( 250 new CacheLoader<String, List<I_CmsFormatterBean>>() { 251 252 @Override 253 public List<I_CmsFormatterBean> load(String typeName) throws Exception { 254 255 List<I_CmsFormatterBean> result = new ArrayList<>(); 256 for (I_CmsFormatterBean formatter : getActiveFormatters().values()) { 257 if (formatter.getResourceTypeNames().contains(typeName)) { 258 result.add(formatter); 259 } 260 } 261 return result; 262 } 263 }); 264 265 /** Cached shared setting overrides. */ 266 private volatile ImmutableList<CmsUUID> m_sharedSettingOverrides; 267 268 /** Set of names of active types.*/ 269 private Set<String> m_typesAddable; 270 271 /** Cache of (active) resource type configurations by name. */ 272 private Map<String, CmsResourceTypeConfig> m_typesByName; 273 274 /** Type names configured in this or ancestor sitemap configurations. */ 275 private Set<String> m_typesInAncestors; 276 277 /** 278 * Creates a new configuration data object, based on an internal configuration data bean and a 279 * configuration cache state.<p> 280 * 281 * @param data the internal configuration data bean 282 * @param cache the configuration cache state 283 * @param configSequence the configuration sequence 284 */ 285 public CmsADEConfigData( 286 CmsADEConfigDataInternal data, 287 CmsADEConfigCacheState cache, 288 CmsADEConfigurationSequence configSequence) { 289 290 m_data = data; 291 m_cache = cache; 292 m_configSequence = configSequence; 293 } 294 295 /** 296 * Generic method to merge lists of named configuration objects.<p> 297 * 298 * The lists are merged such that the configuration objects from the child list rise to the front of the result list, 299 * and two configuration objects will be merged themselves if they share the same name.<p> 300 * 301 * For example, if we have two lists of configuration objects:<p> 302 * 303 * parent: A1, B1, C1<p> 304 * child: D2, B2<p> 305 * 306 * then the resulting list will look like:<p> 307 * 308 * D2, B3, A1, C1<p> 309 * 310 * where B3 is the result of merging B1 and B2.<p> 311 * 312 * @param <C> the type of configuration object 313 * @param parentConfigs the parent configurations 314 * @param childConfigs the child configurations 315 * @param preserveDisabled if true, try to merge parents with disabled children instead of discarding them 316 * 317 * @return the merged configuration object list 318 */ 319 public static <C extends I_CmsConfigurationObject<C>> List<C> combineConfigurationElements( 320 List<C> parentConfigs, 321 List<C> childConfigs, 322 boolean preserveDisabled) { 323 324 List<C> result = new ArrayList<C>(); 325 Map<String, C> map = new LinkedHashMap<String, C>(); 326 if (parentConfigs != null) { 327 for (C parent : Lists.reverse(parentConfigs)) { 328 map.put(parent.getKey(), parent); 329 } 330 } 331 if (childConfigs == null) { 332 childConfigs = Collections.emptyList(); 333 } 334 for (C child : Lists.reverse(childConfigs)) { 335 String childKey = child.getKey(); 336 if (child.isDisabled() && !preserveDisabled) { 337 map.remove(childKey); 338 } else { 339 C parent = map.get(childKey); 340 map.remove(childKey); 341 C newValue; 342 if (parent != null) { 343 newValue = parent.merge(child); 344 } else { 345 newValue = child; 346 } 347 map.put(childKey, newValue); 348 } 349 } 350 result = new ArrayList<C>(map.values()); 351 Collections.reverse(result); 352 // those multiple "reverse" calls may a bit confusing. They are there because on the one hand we want to keep the 353 // configuration items from one configuration in the same order as they are defined, on the other hand we want 354 // configuration items from a child configuration to rise to the top of the configuration items. 355 356 // so for example, if the parent configuration has items with the keys A,B,C,E 357 // and the child configuration has items with the keys C,B,D 358 // we want the items of the combined configuration in the order C,B,D,A,E 359 360 return result; 361 } 362 363 /** 364 * If the given formatter key has a sub-formatter suffix, returns the part before it, 365 * otherwise returns null. 366 * 367 * @param key the formatter key 368 * @return the parent formatter key 369 */ 370 public static final String getParentFormatterKey(String key) { 371 372 if (key == null) { 373 return null; 374 } 375 int separatorPos = key.lastIndexOf(CmsGwtConstants.FORMATTER_SUBKEY_SEPARATOR); 376 if (separatorPos == -1) { 377 return null; 378 } 379 return key.substring(0, separatorPos); 380 381 } 382 383 /** 384 * Applies the formatter change sets of this and all parent configurations to a formatter index 385 * 386 * @param formatterIndex the collection of formatters to apply the changes to 387 * 388 * @param formatterCacheState the formatter cache state from which new external formatters should be fetched 389 */ 390 public void applyAllFormatterChanges( 391 CmsFormatterIndex formatterIndex, 392 CmsFormatterConfigurationCacheState formatterCacheState) { 393 394 for (CmsFormatterChangeSet changeSet : getFormatterChangeSets()) { 395 changeSet.applyToFormatters(formatterIndex, formatterCacheState); 396 } 397 } 398 399 /** 400 * Gets the 'best' formatter for the given ID.<p> 401 * 402 * If the formatter with the ID has a key, then the active formatter with the same key is returned. Otherwise, the 403 * formatter matching the ID is returned. So being active and having the same key is prioritized over an exact ID match. 404 * 405 * @param id the formatter ID 406 * @return the best formatter the given ID 407 */ 408 public I_CmsFormatterBean findFormatter(CmsUUID id) { 409 410 return findFormatter(id, false); 411 } 412 413 /** 414 * Gets the 'best' formatter for the given ID.<p> 415 * 416 * If the formatter with the ID has a key, then the active formatter with the same key is returned. Otherwise, the 417 * formatter matching the ID is returned. So being active and having the same key is prioritized over an exact ID match. 418 * 419 * @param id the formatter ID 420 * @param noWarn if true, disables warnings 421 * @return the best formatter the given ID 422 */ 423 public I_CmsFormatterBean findFormatter(CmsUUID id, boolean noWarn) { 424 425 if (id == null) { 426 return null; 427 } 428 429 CmsFormatterConfigurationCacheState formatterState = getCachedFormatters(); 430 I_CmsFormatterBean originalResult = formatterState.getFormatters().get(id); 431 I_CmsFormatterBean result = originalResult; 432 if ((result != null) && (result.getKey() != null)) { 433 String key = result.getKey(); 434 I_CmsFormatterBean resultForKey = getFormatterAndWarnIfAmbiguous(getActiveFormattersByKey(), key, noWarn); 435 if (resultForKey != null) { 436 result = resultForKey; 437 } else { 438 String parentKey = getParentFormatterKey(key); 439 if (parentKey != null) { 440 resultForKey = getFormatterAndWarnIfAmbiguous(getActiveFormattersByKey(), parentKey, noWarn); 441 if (resultForKey != null) { 442 result = resultForKey; 443 } 444 } 445 } 446 } 447 448 if (result != originalResult) { 449 String message = "Using substitute formatter " 450 + getFormatterLabel(result) 451 + " instead of " 452 + getFormatterLabel(originalResult) 453 + " because of matching key."; 454 LOG.debug(message); 455 OpenCmsServlet.withRequestCache( 456 reqCache -> reqCache.addLog(REQUEST_LOG_CHANNEL, "debug", REQ_LOG_PREFIX + message)); 457 } 458 return result; 459 } 460 461 /** 462 * Gets the 'best' formatter for the given name.<p> 463 * 464 * The name can be either a formatter key, or a formatter UUID. If it's a key, an active formatter with that key is returned. 465 * If it's a UUID, and the formatter with that UUID has no key, it will be returned. If it does have a key, the active formatter 466 * with that key is returned (so being active and having the same key is prioritized over an exact ID match). 467 * 468 * @param name a formatter name (key or ID) 469 * @return the best formatter for that name, or null if no formatter could be found 470 */ 471 public I_CmsFormatterBean findFormatter(String name) { 472 473 return findFormatter(name, false); 474 } 475 476 /** 477 * Gets the 'best' formatter for the given name.<p> 478 * 479 * The name can be either a formatter key, or a formatter UUID. If it's a key, an active formatter with that key is returned. 480 * If it's a UUID, and the formatter with that UUID has no key, it will be returned. If it does have a key, the active formatter 481 * with that key is returned (so being active and having the same key is prioritized over an exact ID match). 482 * 483 * @param name a formatter name (key or ID) 484 * @param noWarn if true, disables warnings 485 * @return the best formatter for that name, or null if no formatter could be found 486 */ 487 public I_CmsFormatterBean findFormatter(String name, boolean noWarn) { 488 489 if (name == null) { 490 return null; 491 } 492 493 if (systemSettingPrefixes.contains(name) || name.startsWith(CmsContainerElement.SYSTEM_SETTING_PREFIX)) { 494 if (LOG.isDebugEnabled()) { 495 LOG.debug("System setting prefix used: " + name, new Exception()); 496 } 497 return null; 498 } 499 500 if (CmsUUID.isValidUUID(name)) { 501 return findFormatter(new CmsUUID(name), noWarn); 502 } 503 504 if (name.startsWith(CmsFormatterConfig.SCHEMA_FORMATTER_ID)) { 505 return null; 506 } 507 508 Multimap<String, I_CmsFormatterBean> active = getActiveFormattersByKey(); 509 I_CmsFormatterBean result = getFormatterAndWarnIfAmbiguous(active, name, noWarn); 510 if (result != null) { 511 return result; 512 } 513 514 String parentName = getParentFormatterKey(name); 515 if (parentName != null) { 516 result = getFormatterAndWarnIfAmbiguous(active, parentName, noWarn); 517 if (result != null) { 518 return result; 519 } 520 } 521 522 if (!noWarn) { 523 String message1 = "No local formatter found for key '" 524 + name 525 + "' at '" 526 + getBasePath() 527 + "', trying inactive formatters"; 528 LOG.warn(message1); 529 OpenCmsServlet.withRequestCache(rc -> rc.addLog(REQUEST_LOG_CHANNEL, "warn", REQ_LOG_PREFIX + message1)); 530 } 531 532 Multimap<String, I_CmsFormatterBean> all = getFormattersByKey(); 533 result = getFormatterAndWarnIfAmbiguous(all, name, noWarn); 534 if (result != null) { 535 return result; 536 } 537 538 if (parentName != null) { 539 result = getFormatterAndWarnIfAmbiguous(all, parentName, noWarn); 540 if (result != null) { 541 return result; 542 } 543 } 544 545 if (!noWarn) { 546 OpenCmsServlet.withRequestCache( 547 rc -> rc.addLog( 548 REQUEST_LOG_CHANNEL, 549 "warn", 550 REQ_LOG_PREFIX + "No formatter found for key '" + name + "' at '" + getBasePath() + "'")); 551 } 552 return null; 553 } 554 555 /** 556 * Gets the active external (non-schema) formatters for this sub-sitemap.<p> 557 * 558 * @return the map of active external formatters by structure id 559 */ 560 public Map<CmsUUID, I_CmsFormatterBean> getActiveFormatters() { 561 562 if (m_activeFormatters == null) { 563 CmsFormatterIndex formatterIndex = new CmsFormatterIndex(); 564 for (I_CmsFormatterBean formatter : getCachedFormatters().getAutoEnabledFormatters().values()) { 565 formatterIndex.addFormatter(formatter); 566 } 567 applyAllFormatterChanges(formatterIndex, getCachedFormatters()); 568 m_activeFormatters = Collections.unmodifiableMap(formatterIndex.getFormattersWithAdditionalKeys()); 569 } 570 return m_activeFormatters; 571 } 572 573 /** 574 * Gets the active formatters for a given container type. 575 * 576 * @param containerType a container type 577 * 578 * @return the active formatters for the container type 579 */ 580 public List<I_CmsFormatterBean> getActiveFormattersWithContainerType(String containerType) { 581 582 return m_formattersByContainerType.computeIfAbsent( 583 containerType, 584 type -> Collections.unmodifiableList( 585 getActiveFormatters().values().stream().filter( 586 formatter -> formatter.getContainerTypes().contains(type)).collect(Collectors.toList()))); 587 } 588 589 /** 590 * Gets the active formatters for a given display type. 591 * 592 * @param displayType a display type 593 * @return the active formatters for the display type 594 */ 595 public List<I_CmsFormatterBean> getActiveFormattersWithDisplayType(String displayType) { 596 597 return m_formattersByDisplayType.computeIfAbsent( 598 displayType, 599 type -> Collections.unmodifiableList( 600 getActiveFormatters().values().stream().filter( 601 formatter -> Objects.equals(type, formatter.getDisplayType())).collect(Collectors.toList())) 602 603 ); 604 } 605 606 /** 607 * Gets the set of names of types active in this sitemap configuration. 608 * 609 * @return the set of type names of active types 610 */ 611 public Set<String> getAddableTypeNames() { 612 613 Set<String> result = m_typesAddable; 614 if (result != null) { 615 return result; 616 } else { 617 Set<String> mutableResult = new HashSet<>(); 618 for (CmsResourceTypeConfig typeConfig : internalGetResourceTypes(true)) { 619 if (!typeConfig.isAddDisabled()) { 620 mutableResult.add(typeConfig.getTypeName()); 621 } 622 } 623 result = Collections.unmodifiableSet(mutableResult); 624 m_typesAddable = result; 625 return result; 626 } 627 } 628 629 /** 630 * Gets the 'add content' restriction for this configuration. 631 * 632 * @return the 'add content' restriction 633 */ 634 public CmsAddContentRestriction getAddContentRestriction() { 635 636 getAncestorTypeNames(); 637 638 CmsADEConfigData parentConfig = parent(); 639 if (parentConfig == null) { 640 return m_data.getAddContentRestriction(); 641 } else { 642 return parentConfig.getAddContentRestriction().merge(m_data.getAddContentRestriction()); 643 } 644 } 645 646 /** 647 * Gets the list of all detail pages.<p> 648 * 649 * @return the list of all detail pages 650 */ 651 public List<CmsDetailPageInfo> getAllDetailPages() { 652 653 return getAllDetailPages(true); 654 } 655 656 /** 657 * Gets a list of all detail pages.<p> 658 * 659 * @param update if true, this method will try to correct the root paths in the returned objects if the corresponding resources have been moved 660 * 661 * @return the list of all detail pages 662 */ 663 public List<CmsDetailPageInfo> getAllDetailPages(boolean update) { 664 665 CmsADEConfigData parentData = parent(); 666 List<CmsDetailPageInfo> parentDetailPages; 667 if (parentData != null) { 668 parentDetailPages = parentData.getAllDetailPages(false); 669 } else { 670 parentDetailPages = Collections.emptyList(); 671 } 672 List<CmsDetailPageInfo> result = mergeDetailPages(parentDetailPages, m_data.getOwnDetailPages()); 673 if (update) { 674 result = updateUris(result); 675 } 676 return result; 677 } 678 679 /** 680 * Gets the set of names of types configured in this or any ancestor sitemap configurations. 681 * 682 * @return the set of type names from all ancestor configurations 683 */ 684 public Set<String> getAncestorTypeNames() { 685 686 Set<String> result = m_typesInAncestors; 687 if (result != null) { 688 return result; 689 } else { 690 Set<String> mutableResult = new HashSet<>(); 691 for (CmsResourceTypeConfig typeConfig : internalGetResourceTypes(false)) { 692 mutableResult.add(typeConfig.getTypeName()); 693 } 694 result = Collections.unmodifiableSet(mutableResult); 695 m_typesInAncestors = result; 696 return result; 697 } 698 } 699 700 /** 701 * Gets the value of an attribute, or a default value 702 * 703 * @param key the attribute key 704 * @param defaultValue the value to return if no attribute with the given name is found 705 * 706 * @return the attribute value 707 */ 708 public String getAttribute(String key, String defaultValue) { 709 710 AttributeValue value = getAttributes().get(key); 711 if (value != null) { 712 return value.getValue(); 713 } else { 714 return defaultValue; 715 } 716 717 } 718 719 /** 720 * Gets the active sitemap attribute editor configuration. 721 * 722 * @return the active sitemap attribute editor configuration 723 */ 724 public CmsSitemapAttributeEditorConfiguration getAttributeEditorConfiguration() { 725 726 CmsUUID id = getAttributeEditorConfigurationId(); 727 CmsSitemapAttributeEditorConfiguration result = m_cache.getAttributeEditorConfiguration(id); 728 if (result == null) { 729 result = CmsSitemapAttributeEditorConfiguration.EMPTY; 730 } 731 return result; 732 733 } 734 735 /** 736 * Gets the structure id of the configured sitemap attribute editor configuration. 737 * 738 * @return the structure id of the configured sitemap attribute editor configuration 739 */ 740 public CmsUUID getAttributeEditorConfigurationId() { 741 742 CmsADEConfigData parent = parent(); 743 CmsUUID result = m_data.getAttributeEditorConfigId(); 744 if ((result == null) && (parent != null)) { 745 result = parent.getAttributeEditorConfigurationId(); 746 } 747 return result; 748 749 } 750 751 /** 752 * Gets the map of attributes configured for this sitemap, including values inherited from parent sitemaps. 753 * 754 * @return the map of attributes 755 */ 756 public Map<String, AttributeValue> getAttributes() { 757 758 if (m_attributes != null) { 759 return m_attributes; 760 } 761 CmsADEConfigData parentConfig = parent(); 762 Map<String, AttributeValue> result = new HashMap<>(); 763 if (parentConfig != null) { 764 result.putAll(parentConfig.getAttributes()); 765 } 766 767 for (Map.Entry<String, AttributeValue> entry : m_data.getAttributes().entrySet()) { 768 result.put(entry.getKey(), entry.getValue()); 769 } 770 Map<String, AttributeValue> immutableResult = Collections.unmodifiableMap(result); 771 m_attributes = immutableResult; 772 return immutableResult; 773 } 774 775 /** 776 * Gets the configuration base path.<p> 777 * 778 * For example, if the configuration file is located at /sites/default/.content/.config, the base path is /sites/default.<p> 779 * 780 * @return the base path of the configuration 781 */ 782 public String getBasePath() { 783 784 return m_data.getBasePath(); 785 } 786 787 /** 788 * Gets the cached formatters.<p> 789 * 790 * @return the cached formatters 791 */ 792 public CmsFormatterConfigurationCacheState getCachedFormatters() { 793 794 if (m_cachedFormatters == null) { 795 m_cachedFormatters = OpenCms.getADEManager().getCachedFormatters( 796 getCms().getRequestContext().getCurrentProject().isOnlineProject()); 797 } 798 return m_cachedFormatters; 799 } 800 801 /** 802 * Gets an (immutable) list of paths of configuration files in inheritance order. 803 * 804 * @return the list of configuration files 805 */ 806 public List<String> getConfigPaths() { 807 808 return m_configSequence.getConfigPaths(); 809 810 } 811 812 /** 813 * Returns the names of the bundles configured as workplace bundles in any module configuration.<p> 814 * 815 * @return the names of the bundles configured as workplace bundles in any module configuration. 816 */ 817 public Set<String> getConfiguredWorkplaceBundles() { 818 819 Set<String> result = new HashSet<String>(); 820 for (CmsResourceTypeConfig config : internalGetResourceTypes(false)) { 821 String bundlename = config.getConfiguredWorkplaceBundle(); 822 if (null != bundlename) { 823 result.add(bundlename); 824 } 825 } 826 return result; 827 } 828 829 /** 830 * Gets the content folder path.<p> 831 * 832 * For example, if the configuration file is located at /sites/default/.content/.config, the content folder path is /sites/default/.content 833 * 834 * @return the content folder path 835 */ 836 public String getContentFolderPath() { 837 838 return CmsStringUtil.joinPaths(m_data.getBasePath(), CmsADEManager.CONTENT_FOLDER_NAME); 839 840 } 841 842 /** 843 * Returns a list of the creatable resource types.<p> 844 * 845 * @param cms the CMS context used to check whether the resource types are creatable 846 * @param pageFolderRootPath the root path of the current container page 847 * @return the list of creatable resource type 848 * 849 * @throws CmsException if something goes wrong 850 */ 851 public List<CmsResourceTypeConfig> getCreatableTypes(CmsObject cms, String pageFolderRootPath) throws CmsException { 852 853 List<CmsResourceTypeConfig> result = new ArrayList<CmsResourceTypeConfig>(); 854 for (CmsResourceTypeConfig typeConfig : getResourceTypes()) { 855 if (typeConfig.checkCreatable(cms, pageFolderRootPath)) { 856 result.add(typeConfig); 857 } 858 } 859 return result; 860 } 861 862 /** 863 * Returns the default detail page.<p> 864 * 865 * @return the default detail page 866 */ 867 public CmsDetailPageInfo getDefaultDetailPage() { 868 869 for (CmsDetailPageInfo detailpage : getAllDetailPages(true)) { 870 if (CmsADEManager.DEFAULT_DETAILPAGE_TYPE.equals(detailpage.getType())) { 871 return detailpage; 872 } 873 } 874 return null; 875 } 876 877 /** 878 * Returns the default model page.<p> 879 * 880 * @return the default model page 881 */ 882 public CmsModelPageConfig getDefaultModelPage() { 883 884 List<CmsModelPageConfig> modelPages = getModelPages(); 885 for (CmsModelPageConfig modelPageConfig : getModelPages()) { 886 if (modelPageConfig.isDefault()) { 887 return modelPageConfig; 888 } 889 } 890 if (modelPages.isEmpty()) { 891 return null; 892 } 893 return modelPages.get(0); 894 } 895 896 /** 897 * Gets the detail information for this sitemap config data object.<p> 898 * 899 * @param cms the CMS context 900 * @return the list of detail information 901 */ 902 public List<DetailInfo> getDetailInfos(CmsObject cms) { 903 904 List<DetailInfo> result = Lists.newArrayList(); 905 List<CmsDetailPageInfo> detailPages = getAllDetailPages(true); 906 Collections.reverse(detailPages); // make sure primary detail pages come later in the list and override other detail pages for the same type 907 Map<String, CmsDetailPageInfo> primaryDetailPageMapByType = Maps.newHashMap(); 908 for (CmsDetailPageInfo pageInfo : detailPages) { 909 primaryDetailPageMapByType.put(pageInfo.getType(), pageInfo); 910 } 911 for (CmsResourceTypeConfig typeConfig : getResourceTypes()) { 912 String typeName = typeConfig.getTypeName(); 913 if (((typeConfig.getFolderOrName() == null) || !typeConfig.getFolderOrName().isPageRelative()) 914 && primaryDetailPageMapByType.containsKey(typeName)) { 915 String folderPath = typeConfig.getFolderPath(cms, null); 916 CmsDetailPageInfo pageInfo = primaryDetailPageMapByType.get(typeName); 917 result.add(new DetailInfo(folderPath, pageInfo, typeName, getBasePath())); 918 } 919 } 920 return result; 921 } 922 923 /** 924 * Gets the detail pages for a specific type.<p> 925 * 926 * @param type the type name 927 * 928 * @return the list of detail pages for that type 929 */ 930 public List<CmsDetailPageInfo> getDetailPagesForType(String type) { 931 932 List<CmsDetailPageInfo> result = new ArrayList<CmsDetailPageInfo>(); 933 CmsResourceTypeConfig typeConfig = getResourceType(type); 934 if (type.startsWith(CmsDetailPageInfo.FUNCTION_PREFIX) 935 || ((typeConfig != null) && !typeConfig.isDetailPagesDisabled())) { 936 937 List<CmsDetailPageInfo> defaultPages = new ArrayList<>(); 938 for (CmsDetailPageInfo detailpage : getAllDetailPages(true)) { 939 if (detailpage.getType().equals(type)) { 940 result.add(detailpage); 941 } else if (CmsADEManager.DEFAULT_DETAILPAGE_TYPE.equals(detailpage.getType())) { 942 defaultPages.add(detailpage); 943 } 944 } 945 result.addAll(defaultPages); 946 } 947 return result; 948 } 949 950 /** 951 * Returns the direct edit permissions for e.g. list elements with the given type. 952 * 953 * @param type the resource type name 954 * @return the permissions 955 */ 956 public SitemapDirectEditPermissions getDirectEditPermissions(String type) { 957 958 if (type == null) { 959 LOG.error("Null type in checkListEdit"); 960 return SitemapDirectEditPermissions.all; 961 } 962 963 if (!getAncestorTypeNames().contains(type)) { 964 // not configured anywhere for ADE 965 return SitemapDirectEditPermissions.editAndCreate; 966 } 967 968 CmsResourceTypeConfig typeConfig = getResourceType(type); 969 if (typeConfig == null) { 970 return SitemapDirectEditPermissions.none; 971 } 972 973 if (typeConfig.isEnabledInLists()) { 974 return SitemapDirectEditPermissions.editAndCreate; 975 } 976 977 if (typeConfig.isCreateDisabled() || typeConfig.isAddDisabled()) { 978 return SitemapDirectEditPermissions.editOnly; 979 } 980 981 return SitemapDirectEditPermissions.all; 982 } 983 984 /** 985 * Gets the display mode for deactivated functions in the gallery dialog. 986 * 987 * @param defaultValue the default value to return if it's not set 988 * @return the display mode for deactivated types 989 */ 990 public CmsGalleryDisabledTypesMode getDisabledFunctionsMode(CmsGalleryDisabledTypesMode defaultValue) { 991 992 CmsADEConfigData parentData = parent(); 993 if (m_data.getGalleryDisabledFunctionsMode() != null) { 994 return m_data.getGalleryDisabledFunctionsMode(); 995 } else if (parentData != null) { 996 return parentData.getDisabledFunctionsMode(defaultValue); 997 } else { 998 return defaultValue; 999 } 1000 } 1001 1002 /** 1003 * Gets the display mode for deactivated types in the gallery dialog. 1004 * 1005 * @param defaultValue the default value to return if it's not set 1006 * @return the display mode for deactivated types 1007 */ 1008 public CmsGalleryDisabledTypesMode getDisabledTypeMode(CmsGalleryDisabledTypesMode defaultValue) { 1009 1010 CmsADEConfigData parentData = parent(); 1011 if (m_data.getDisabledTypeMode() != null) { 1012 return m_data.getDisabledTypeMode(); 1013 } else if (parentData != null) { 1014 return parentData.getDisabledTypeMode(defaultValue); 1015 } else { 1016 return defaultValue; 1017 } 1018 } 1019 1020 /** 1021 * Returns all available display formatters.<p> 1022 * 1023 * @param cms the cms context 1024 * 1025 * @return the available display formatters 1026 */ 1027 public List<I_CmsFormatterBean> getDisplayFormatters(CmsObject cms) { 1028 1029 List<I_CmsFormatterBean> result = new ArrayList<I_CmsFormatterBean>(); 1030 for (I_CmsFormatterBean formatter : getCachedFormatters().getFormatters().values()) { 1031 if (formatter.isDisplayFormatter()) { 1032 result.add(formatter); 1033 } 1034 } 1035 return result; 1036 } 1037 1038 /** 1039 * Gets the bean that represents the dynamic function availability. 1040 * 1041 * @param formatterConfig the formatter configuration state 1042 * 1043 * @return the dynamic function availability 1044 */ 1045 public CmsFunctionAvailability getDynamicFunctionAvailability(CmsFormatterConfigurationCacheState formatterConfig) { 1046 1047 CmsADEConfigData parentData = parent(); 1048 CmsFunctionAvailability result; 1049 if (parentData == null) { 1050 result = new CmsFunctionAvailability(formatterConfig); 1051 } else { 1052 result = parentData.getDynamicFunctionAvailability(formatterConfig); 1053 } 1054 Collection<CmsUUID> enabledIds = m_data.getDynamicFunctions(); 1055 Collection<CmsUUID> disabledIds = m_data.getFunctionsToRemove(); 1056 if (m_data.isRemoveAllFunctions() && !m_configSequence.getMeta().isSkipRemovals()) { 1057 result.removeAll(); 1058 } 1059 if (enabledIds != null) { 1060 result.addAll(enabledIds); 1061 } 1062 if (disabledIds != null) { 1063 for (CmsUUID id : disabledIds) { 1064 result.remove(id); 1065 } 1066 } 1067 return result; 1068 } 1069 1070 /** 1071 * Gets the root path of the closest subsite going up the tree which has the 'exclude external detail contents' option enabled, or '/' if no such subsite exists. 1072 * 1073 * @return the root path of the closest subsite with 'external detail contents excluded' 1074 */ 1075 public String getExternalDetailContentExclusionFolder() { 1076 1077 if (m_data.isExcludeExternalDetailContents()) { 1078 String basePath = m_data.getBasePath(); 1079 if (basePath == null) { 1080 return "/"; 1081 } else { 1082 return basePath; 1083 } 1084 } else { 1085 CmsADEConfigData parent = parent(); 1086 if (parent != null) { 1087 return parent.getExternalDetailContentExclusionFolder(); 1088 } else { 1089 return "/"; 1090 } 1091 } 1092 } 1093 1094 /** 1095 * Returns the formatter change sets for this and all parent sitemaps, ordered by increasing folder depth of the sitemap.<p> 1096 * 1097 * @return the formatter change sets for all ancestor sitemaps 1098 */ 1099 public List<CmsFormatterChangeSet> getFormatterChangeSets() { 1100 1101 CmsADEConfigData currentConfig = this; 1102 List<CmsFormatterChangeSet> result = Lists.newArrayList(); 1103 while (currentConfig != null) { 1104 CmsFormatterChangeSet changes = currentConfig.getOwnFormatterChangeSet(); 1105 if (changes != null) { 1106 if (currentConfig.getMeta().isSkipRemovals()) { 1107 changes = changes.cloneWithNoRemovals(); 1108 } 1109 result.add(changes); 1110 } 1111 currentConfig = currentConfig.parent(); 1112 } 1113 Collections.reverse(result); 1114 return result; 1115 } 1116 1117 /** 1118 * Gets the formatter configuration for a resource.<p> 1119 * 1120 * @param cms the current CMS context 1121 * @param res the resource for which the formatter configuration should be retrieved 1122 * 1123 * @return the configuration of formatters for the resource 1124 */ 1125 public CmsFormatterConfiguration getFormatters(CmsObject cms, CmsResource res) { 1126 1127 if (CmsResourceTypeFunctionConfig.isFunction(res)) { 1128 1129 CmsFormatterConfigurationCacheState formatters = getCachedFormatters(); 1130 I_CmsFormatterBean function = findFormatter(res.getStructureId()); 1131 if (function != null) { 1132 return CmsFormatterConfiguration.create(cms, Collections.singletonList(function)); 1133 } else { 1134 if ((!res.getStructureId().isNullUUID()) 1135 && cms.existsResource(res.getStructureId(), CmsResourceFilter.IGNORE_EXPIRATION)) { 1136 // usually if it's just been created, but not added to the configuration cache yet 1137 CmsFormatterBeanParser parser = new CmsFormatterBeanParser(cms, new HashMap<>()); 1138 try { 1139 function = parser.parse( 1140 CmsXmlContentFactory.unmarshal(cms, cms.readFile(res)), 1141 res.getRootPath(), 1142 "" + res.getStructureId()); 1143 return CmsFormatterConfiguration.create(cms, Collections.singletonList(function)); 1144 } catch (Exception e) { 1145 LOG.warn(e.getLocalizedMessage(), e); 1146 return CmsFormatterConfiguration.EMPTY_CONFIGURATION; 1147 } 1148 1149 } else { 1150 // if a new function has been dragged on the page, it doesn't exist in the VFS yet, so we need a different 1151 // instance as a replacement 1152 CmsResource defaultFormatter = CmsFunctionRenderer.getDefaultFunctionInstance(cms); 1153 if (defaultFormatter != null) { 1154 I_CmsFormatterBean defaultFormatterBean = formatters.getFormatters().get( 1155 defaultFormatter.getStructureId()); 1156 return CmsFormatterConfiguration.create(cms, Collections.singletonList(defaultFormatterBean)); 1157 } else { 1158 LOG.warn("Could not read default formatter for functions."); 1159 return CmsFormatterConfiguration.EMPTY_CONFIGURATION; 1160 } 1161 } 1162 } 1163 } else { 1164 try { 1165 int resTypeId = res.getTypeId(); 1166 return getFormatters( 1167 cms, 1168 OpenCms.getResourceManager().getResourceType(resTypeId), 1169 getFormattersFromSchema(cms, res)); 1170 } catch (CmsLoaderException e) { 1171 LOG.warn(e.getLocalizedMessage(), e); 1172 return CmsFormatterConfiguration.EMPTY_CONFIGURATION; 1173 } 1174 } 1175 } 1176 1177 /** 1178 * Gets a named function reference.<p> 1179 * 1180 * @param name the name of the function reference 1181 * 1182 * @return the function reference for the given name 1183 */ 1184 public CmsFunctionReference getFunctionReference(String name) { 1185 1186 List<CmsFunctionReference> functionReferences = getFunctionReferences(); 1187 for (CmsFunctionReference functionRef : functionReferences) { 1188 if (functionRef.getName().equals(name)) { 1189 return functionRef; 1190 } 1191 } 1192 return null; 1193 } 1194 1195 /** 1196 * Gets the list of configured function references.<p> 1197 * 1198 * @return the list of configured function references 1199 */ 1200 public List<CmsFunctionReference> getFunctionReferences() { 1201 1202 return internalGetFunctionReferences(); 1203 } 1204 1205 /** 1206 * Gets the map of external (non-schema) formatters which are inactive in this sub-sitemap.<p> 1207 * 1208 * @return the map inactive external formatters 1209 */ 1210 public Map<CmsUUID, I_CmsFormatterBean> getInactiveFormatters() { 1211 1212 CmsFormatterConfigurationCacheState cacheState = getCachedFormatters(); 1213 Map<CmsUUID, I_CmsFormatterBean> result = Maps.newHashMap(cacheState.getFormatters()); 1214 result.keySet().removeAll(getActiveFormatters().keySet()); 1215 return result; 1216 } 1217 1218 /** 1219 * Gets the list of available model pages.<p> 1220 * 1221 * @return the list of available model pages 1222 */ 1223 public List<CmsModelPageConfig> getModelPages() { 1224 1225 return getModelPages(false); 1226 } 1227 1228 /** 1229 * Gets the list of available model pages.<p> 1230 * 1231 * @param includeDisable <code>true</code> to include disabled model pages 1232 * 1233 * @return the list of available model pages 1234 */ 1235 public List<CmsModelPageConfig> getModelPages(boolean includeDisable) { 1236 1237 CmsADEConfigData parentData = parent(); 1238 List<CmsModelPageConfig> parentModelPages; 1239 if ((parentData != null) && !m_data.isDiscardInheritedModelPages()) { 1240 parentModelPages = parentData.getModelPages(); 1241 } else { 1242 parentModelPages = Collections.emptyList(); 1243 } 1244 1245 List<CmsModelPageConfig> result = combineConfigurationElements( 1246 parentModelPages, 1247 m_data.getOwnModelPageConfig(), 1248 includeDisable); 1249 return result; 1250 } 1251 1252 /** 1253 * Gets the formatter changes for this sitemap configuration.<p> 1254 * 1255 * @return the formatter change set 1256 */ 1257 public CmsFormatterChangeSet getOwnFormatterChangeSet() { 1258 1259 return m_data.getFormatterChangeSet(); 1260 } 1261 1262 /** 1263 * Gets the configuration for the available properties.<p> 1264 * 1265 * @return the configuration for the available properties 1266 */ 1267 public List<CmsPropertyConfig> getPropertyConfiguration() { 1268 1269 CmsADEConfigData parentData = parent(); 1270 List<CmsPropertyConfig> parentProperties; 1271 boolean removeInherited = m_data.isDiscardInheritedProperties() && !getMeta().isSkipRemovals(); 1272 if ((parentData != null) && !removeInherited) { 1273 parentProperties = parentData.getPropertyConfiguration(); 1274 } else { 1275 parentProperties = Collections.emptyList(); 1276 } 1277 LinkedHashMap<String, CmsPropertyConfig> propMap = new LinkedHashMap<>(); 1278 for (CmsPropertyConfig conf : parentProperties) { 1279 if (conf.isDisabled()) { 1280 continue; 1281 } 1282 propMap.put(conf.getName(), conf); 1283 } 1284 for (CmsPropertyConfig conf : m_data.getOwnPropertyConfigurations()) { 1285 if (conf.isDisabled()) { 1286 propMap.remove(conf.getName()); 1287 } else if (propMap.containsKey(conf.getName())) { 1288 propMap.put(conf.getName(), propMap.get(conf.getName()).merge(conf)); 1289 } else { 1290 propMap.put(conf.getName(), conf); 1291 } 1292 } 1293 List<CmsPropertyConfig> result = new ArrayList<>(propMap.values()); 1294 return result; 1295 } 1296 1297 /** 1298 * Computes the ordered map of properties to display in the property dialog, given the map of default property configurations passed as a parameter. 1299 * 1300 * @param defaultProperties the default property configurations 1301 * @return the ordered map of property configurations for the property dialog 1302 */ 1303 public Map<String, CmsXmlContentProperty> getPropertyConfiguration( 1304 Map<String, CmsXmlContentProperty> defaultProperties) { 1305 1306 List<CmsPropertyConfig> myPropConfigs = getPropertyConfiguration(); 1307 Map<String, CmsXmlContentProperty> allProps = new LinkedHashMap<>(defaultProperties); 1308 Map<String, CmsXmlContentProperty> result = new LinkedHashMap<>(); 1309 for (CmsPropertyConfig prop : myPropConfigs) { 1310 allProps.put(prop.getName(), prop.getPropertyData()); 1311 if (prop.isTop()) { 1312 result.put(prop.getName(), prop.getPropertyData()); 1313 } 1314 } 1315 for (Map.Entry<String, CmsXmlContentProperty> entry : allProps.entrySet()) { 1316 if (!result.containsKey(entry.getKey())) { 1317 result.put(entry.getKey(), entry.getValue()); 1318 } 1319 } 1320 return result; 1321 1322 } 1323 1324 /** 1325 * Gets the property configuration as a map of CmsXmlContentProperty instances.<p> 1326 * 1327 * @return the map of property configurations 1328 */ 1329 public Map<String, CmsXmlContentProperty> getPropertyConfigurationAsMap() { 1330 1331 Map<String, CmsXmlContentProperty> result = new LinkedHashMap<String, CmsXmlContentProperty>(); 1332 for (CmsPropertyConfig propConf : getPropertyConfiguration()) { 1333 result.put(propConf.getName(), propConf.getPropertyData()); 1334 } 1335 return result; 1336 } 1337 1338 /** 1339 * Returns the resource from which this configuration was read.<p> 1340 * 1341 * @return the resource from which this configuration was read 1342 */ 1343 public CmsResource getResource() { 1344 1345 return m_data.getResource(); 1346 } 1347 1348 /** 1349 * Returns the configuration for a specific resource type.<p> 1350 * 1351 * @param typeName the name of the type 1352 * 1353 * @return the resource type configuration for that type 1354 */ 1355 public CmsResourceTypeConfig getResourceType(String typeName) { 1356 1357 for (CmsResourceTypeConfig type : getResourceTypes()) { 1358 if (typeName.equals(type.getTypeName())) { 1359 return type; 1360 } 1361 } 1362 return null; 1363 } 1364 1365 /** 1366 * Gets a list of all available resource type configurations.<p> 1367 * 1368 * @return the available resource type configurations 1369 */ 1370 public List<CmsResourceTypeConfig> getResourceTypes() { 1371 1372 List<CmsResourceTypeConfig> result = internalGetResourceTypes(true); 1373 for (CmsResourceTypeConfig config : result) { 1374 config.initialize(getCms()); 1375 } 1376 return result; 1377 } 1378 1379 /** 1380 * Gets the searchable resource type configurations.<p> 1381 * 1382 * @param cms the current CMS context 1383 * @return the searchable resource type configurations 1384 */ 1385 public Collection<CmsResourceTypeConfig> getSearchableTypes(CmsObject cms) { 1386 1387 return getResourceTypes(); 1388 } 1389 1390 /** 1391 * Gets the list of structure ids of the shared setting overrides, ordered by increasing specificity. 1392 * 1393 * @return the list of structure ids of shared setting overrides 1394 */ 1395 public ImmutableList<CmsUUID> getSharedSettingOverrides() { 1396 1397 if (m_sharedSettingOverrides != null) { 1398 return m_sharedSettingOverrides; 1399 } 1400 1401 CmsADEConfigData currentConfig = this; 1402 List<CmsADEConfigData> relevantConfigurations = new ArrayList<>(); 1403 while (currentConfig != null) { 1404 relevantConfigurations.add(currentConfig); 1405 if (currentConfig.m_data.isRemoveSharedSettingOverrides() 1406 && !currentConfig.m_configSequence.getMeta().isSkipRemovals()) { 1407 // once we find a configuration where 'remove all shared setting overrides' is enabled, 1408 // all parent configurations become irrelevant 1409 break; 1410 } 1411 currentConfig = currentConfig.parent(); 1412 } 1413 1414 // order by ascending specificity 1415 Collections.reverse(relevantConfigurations); 1416 1417 List<CmsUUID> ids = new ArrayList<>(); 1418 for (CmsADEConfigData config : relevantConfigurations) { 1419 CmsUUID id = config.m_data.getSharedSettingOverride(); 1420 if (id != null) { 1421 ids.add(id); 1422 } 1423 } 1424 ImmutableList<CmsUUID> result = ImmutableList.copyOf(ids); 1425 m_sharedSettingOverrides = result; 1426 return result; 1427 } 1428 1429 /** 1430 * Gets the ids of site plugins which are active in this sitemap configuration. 1431 * 1432 * @return the ids of active site plugins 1433 */ 1434 public Set<CmsUUID> getSitePluginIds() { 1435 1436 CmsADEConfigData parent = parent(); 1437 Set<CmsUUID> result; 1438 if ((parent == null) || (m_data.isRemoveAllPlugins() && !getMeta().isSkipRemovals())) { 1439 result = new HashSet<>(); 1440 } else { 1441 result = parent.getSitePluginIds(); 1442 } 1443 result.removeAll(m_data.getRemovedPlugins()); 1444 result.addAll(m_data.getAddedPlugins()); 1445 return result; 1446 } 1447 1448 /** 1449 * Gets the list of site plugins active in this sitemap configuration. 1450 * 1451 * @return the list of active site plugins 1452 */ 1453 public List<CmsSitePlugin> getSitePlugins() { 1454 1455 Set<CmsUUID> pluginIds = getSitePluginIds(); 1456 List<CmsSitePlugin> result = new ArrayList<>(); 1457 Map<CmsUUID, CmsSitePlugin> plugins = m_cache.getSitePlugins(); 1458 for (CmsUUID id : pluginIds) { 1459 CmsSitePlugin sitePlugin = plugins.get(id); 1460 if (sitePlugin != null) { 1461 result.add(sitePlugin); 1462 } 1463 } 1464 return result; 1465 } 1466 1467 /** 1468 * Gets the type ordering mode. 1469 * 1470 * @return the type ordering mode 1471 */ 1472 public CmsTypeOrderingMode getTypeOrderingMode() { 1473 1474 CmsTypeOrderingMode ownOrderingMode = m_data.getTypeOrderingMode(); 1475 if (ownOrderingMode != null) { 1476 return ownOrderingMode; 1477 } else { 1478 CmsADEConfigData parentConfig = parent(); 1479 CmsTypeOrderingMode parentMode = null; 1480 if (parentConfig == null) { 1481 parentMode = CmsTypeOrderingMode.latestOnTop; 1482 } else { 1483 parentMode = parentConfig.getTypeOrderingMode(); 1484 } 1485 return parentMode; 1486 } 1487 1488 } 1489 1490 /** 1491 * Gets a map of the active resource type configurations, with type names as keys. 1492 * 1493 * @return the map of active types 1494 */ 1495 public Map<String, CmsResourceTypeConfig> getTypesByName() { 1496 1497 if (m_typesByName != null) { 1498 return m_typesByName; 1499 } 1500 Map<String, CmsResourceTypeConfig> result = new HashMap<>(); 1501 for (CmsResourceTypeConfig type : getResourceTypes()) { 1502 result.put(type.getTypeName(), type); 1503 } 1504 result = Collections.unmodifiableMap(result); 1505 m_typesByName = result; 1506 return result; 1507 } 1508 1509 /** 1510 * Gets the set of resource type names for which schema formatters can be enabled or disabled and which are not disabled in this sub-sitemap.<p> 1511 * 1512 * @return the set of types for which schema formatters are active 1513 */ 1514 public Set<String> getTypesWithActiveSchemaFormatters() { 1515 1516 Set<String> result = Sets.newHashSet(getTypesWithModifiableFormatters()); 1517 for (CmsFormatterChangeSet changeSet : getFormatterChangeSets()) { 1518 changeSet.applyToTypes(result); 1519 } 1520 return result; 1521 } 1522 1523 /** 1524 * Gets the set of names of resource types which have schema-based formatters that can be enabled or disabled.<p> 1525 * 1526 * @return the set of names of resource types which have schema-based formatters that can be enabled or disabled 1527 */ 1528 public Set<String> getTypesWithModifiableFormatters() { 1529 1530 Set<String> result = new HashSet<String>(); 1531 for (I_CmsResourceType type : OpenCms.getResourceManager().getResourceTypes()) { 1532 if (type instanceof CmsResourceTypeXmlContent) { 1533 CmsXmlContentDefinition contentDef = null; 1534 try { 1535 contentDef = CmsXmlContentDefinition.getContentDefinitionForType(getCms(), type.getTypeName()); 1536 if ((contentDef != null) && contentDef.getContentHandler().hasModifiableFormatters()) { 1537 result.add(type.getTypeName()); 1538 } 1539 } catch (Exception e) { 1540 LOG.error(e.getLocalizedMessage(), e); 1541 } 1542 } 1543 } 1544 return result; 1545 1546 } 1547 1548 /** 1549 * Checks if there are any matching formatters for the given set of containers.<p> 1550 * 1551 * @param cms the current CMS context 1552 * @param resType the resource type for which the formatter configuration should be retrieved 1553 * @param containers the page containers 1554 * 1555 * @return if there are any matching formatters 1556 */ 1557 public boolean hasFormatters(CmsObject cms, I_CmsResourceType resType, Collection<CmsContainer> containers) { 1558 1559 try { 1560 if (CmsXmlDynamicFunctionHandler.TYPE_FUNCTION.equals(resType.getTypeName()) 1561 || CmsResourceTypeFunctionConfig.TYPE_NAME.equals(resType.getTypeName())) { 1562 // dynamic function may match any container 1563 return true; 1564 } 1565 CmsXmlContentDefinition def = CmsXmlContentDefinition.getContentDefinitionForType( 1566 cms, 1567 resType.getTypeName()); 1568 CmsFormatterConfiguration schemaFormatters = def.getContentHandler().getFormatterConfiguration(cms, null); 1569 CmsFormatterConfiguration formatters = getFormatters(cms, resType, schemaFormatters); 1570 for (CmsContainer cont : containers) { 1571 if (cont.isEditable() 1572 && (formatters.getAllMatchingFormatters(cont.getType(), cont.getWidth()).size() > 0)) { 1573 return true; 1574 } 1575 } 1576 } catch (CmsException e) { 1577 LOG.warn(e.getLocalizedMessage(), e); 1578 1579 } 1580 return false; 1581 } 1582 1583 /** 1584 * Returns the value of the "create contents locally" flag.<p> 1585 * 1586 * If this flag is set, contents of types configured in a super-sitemap will be created in the sub-sitemap (if the user 1587 * creates them from the sub-sitemap). 1588 * 1589 * @return the "create contents locally" flag 1590 */ 1591 public boolean isCreateContentsLocally() { 1592 1593 return m_data.isCreateContentsLocally(); 1594 } 1595 1596 /** 1597 * Returns the value of the "discard inherited model pages" flag.<p> 1598 * 1599 * If this flag is set, inherited model pages will be discarded for this sitemap.<p> 1600 * 1601 * @return the "discard inherited model pages" flag 1602 */ 1603 public boolean isDiscardInheritedModelPages() { 1604 1605 return m_data.isDiscardInheritedModelPages(); 1606 } 1607 1608 /** 1609 * Returns the value of the "discard inherited properties" flag.<p> 1610 * 1611 * If this is flag is set, inherited property definitions will be discarded for this sitemap.<p> 1612 * 1613 * @return the "discard inherited properties" flag.<p> 1614 */ 1615 public boolean isDiscardInheritedProperties() { 1616 1617 return m_data.isDiscardInheritedProperties(); 1618 } 1619 1620 /** 1621 * Returns the value of the "discard inherited types" flag.<p> 1622 * 1623 * If this flag is set, inherited resource types from a super-sitemap will be discarded for this sitemap.<p> 1624 * 1625 * @return the "discard inherited types" flag 1626 */ 1627 public boolean isDiscardInheritedTypes() { 1628 1629 return m_data.isDiscardInheritedTypes(); 1630 } 1631 1632 /** 1633 * True if detail contents outside this sitemap should not be rendered in detail pages from this sitemap. 1634 * 1635 * @return true if detail contents outside this sitemap should not be rendered in detail pages from this sitemap. 1636 */ 1637 public boolean isExcludeExternalDetailContents() { 1638 1639 return m_data.isExcludeExternalDetailContents(); 1640 } 1641 1642 /** 1643 * Checks if dynamic functions not matching any containers should be hidden. 1644 * 1645 * @return true if dynamic functions not matching any containers should be hidden 1646 */ 1647 public boolean isHideNonMatchingFunctions() { 1648 1649 return getDisabledFunctionsMode(CmsGalleryDisabledTypesMode.hide) == CmsGalleryDisabledTypesMode.hide; 1650 } 1651 1652 /** 1653 * Returns true if the subsite should be included in the site selector. 1654 * 1655 * @return true if the subsite should be included in the site selector 1656 */ 1657 public boolean isIncludeInSiteSelector() { 1658 1659 return m_configSequence.getConfig().isIncludeInSiteSelector(); 1660 } 1661 1662 /** 1663 * Returns true if this is a module configuration instead of a normal sitemap configuration.<p> 1664 * 1665 * @return true if this is a module configuration 1666 */ 1667 public boolean isModuleConfiguration() { 1668 1669 return m_data.isModuleConfig(); 1670 } 1671 1672 /** 1673 * Returns true if detail pages from this sitemap should be preferred for links to contents in this sitemap.<p> 1674 * 1675 * @return true if detail pages from this sitemap should be preferred for links to contents in this sitemap 1676 */ 1677 public boolean isPreferDetailPagesForLocalContents() { 1678 1679 return m_data.isPreferDetailPagesForLocalContents(); 1680 } 1681 1682 /** 1683 * Checks if any formatter with the given JSP id has the 'search content' option set to true. 1684 * 1685 * @param jspId the structure id of a formatter JSP 1686 * @return true if any of the formatters 1687 */ 1688 public boolean isSearchContentFormatter(CmsUUID jspId) { 1689 1690 for (I_CmsFormatterBean formatter : getFormattersByJspId().get(jspId)) { 1691 if (formatter.isSearchContent()) { 1692 return true; 1693 } 1694 } 1695 return false; 1696 } 1697 1698 /** 1699 * Returns true if the new container page format, which uses formatter keys (but also is different in other ways from the new format 1700 * 1701 * @return true if formatter keys should be used 1702 */ 1703 public boolean isUseFormatterKeys() { 1704 1705 Boolean result = m_data.getUseFormatterKeys(); 1706 if (result != null) { 1707 LOG.debug("isUseFormatterKeys - found value " + result + " at " + getBasePath()); 1708 return result.booleanValue(); 1709 } 1710 CmsADEConfigData parent = parent(); 1711 if (parent != null) { 1712 return parent.isUseFormatterKeys(); 1713 } 1714 boolean defaultValue = true; 1715 LOG.debug("isUseFormatterKeys - using defaultValue " + defaultValue); 1716 return defaultValue; 1717 } 1718 1719 /** 1720 * Fetches the parent configuration of this configuration.<p> 1721 * 1722 * If this configuration is a sitemap configuration with no direct parent configuration, 1723 * the module configuration will be returned. If this configuration already is a module configuration, 1724 * null will be returned.<p> 1725 * 1726 * @return the parent configuration 1727 */ 1728 public CmsADEConfigData parent() { 1729 1730 Optional<CmsADEConfigurationSequence> parentPath = m_configSequence.getParent(); 1731 if (parentPath.isPresent()) { 1732 CmsADEConfigDataInternal internalData = parentPath.get().getConfig(); 1733 return new CmsADEConfigData(internalData, m_cache, parentPath.get()); 1734 } else { 1735 return null; 1736 } 1737 } 1738 1739 /** 1740 * Returns true if the sitemap attribute editor should be available in this subsite. 1741 * 1742 * @return true if the sitemap attribute editor dialog should be available 1743 */ 1744 public boolean shouldShowSitemapAttributeDialog() { 1745 1746 return getAttributeEditorConfiguration().getAttributeDefinitions().size() > 0; 1747 } 1748 1749 /** 1750 * Clears the internal formatter caches. 1751 * 1752 * <p>This should only be used for test cases. 1753 */ 1754 protected void clearCaches() { 1755 1756 m_activeFormatters = null; 1757 m_activeFormattersByKey = null; 1758 m_formattersByKey = null; 1759 m_formattersByJspId = null; 1760 m_formattersByTypeCache.invalidateAll(); 1761 } 1762 1763 /** 1764 * Creates the content directory for this configuration node if possible.<p> 1765 * 1766 * @throws CmsException if something goes wrong 1767 */ 1768 protected void createContentDirectory() throws CmsException { 1769 1770 if (!isModuleConfiguration()) { 1771 String contentFolder = getContentFolderPath(); 1772 if (!getCms().existsResource(contentFolder)) { 1773 getCms().createResource( 1774 contentFolder, 1775 OpenCms.getResourceManager().getResourceType(CmsResourceTypeFolder.getStaticTypeName())); 1776 } 1777 } 1778 } 1779 1780 /** 1781 * Gets the CMS object used for VFS operations.<p> 1782 * 1783 * @return the CMS object used for VFS operations 1784 */ 1785 protected CmsObject getCms() { 1786 1787 return m_cache.getCms(); 1788 } 1789 1790 /** 1791 * Gets the CMS object used for VFS operations.<p> 1792 * 1793 * @return the CMS object 1794 */ 1795 protected CmsObject getCmsObject() { 1796 1797 return getCms(); 1798 } 1799 1800 /** 1801 * Helper method to converts a list of detail pages to a map from type names to lists of detail pages for each type.<p> 1802 * 1803 * @param detailPages the list of detail pages 1804 * 1805 * @return the map of detail pages 1806 */ 1807 protected Map<String, List<CmsDetailPageInfo>> getDetailPagesMap(List<CmsDetailPageInfo> detailPages) { 1808 1809 Map<String, List<CmsDetailPageInfo>> result = Maps.newHashMap(); 1810 for (CmsDetailPageInfo detailpage : detailPages) { 1811 String type = detailpage.getType(); 1812 if (!result.containsKey(type)) { 1813 result.put(type, new ArrayList<CmsDetailPageInfo>()); 1814 } 1815 result.get(type).add(detailpage); 1816 } 1817 return result; 1818 } 1819 1820 /** 1821 * Collects the folder types in a map.<p> 1822 * 1823 * @return the map of folder types 1824 * 1825 * @throws CmsException if something goes wrong 1826 */ 1827 protected Map<String, String> getFolderTypes() throws CmsException { 1828 1829 Map<String, String> result = new HashMap<String, String>(); 1830 CmsObject cms = OpenCms.initCmsObject(getCms()); 1831 if (m_data.isModuleConfig()) { 1832 Set<String> siteRoots = OpenCms.getSiteManager().getSiteRoots(); 1833 for (String siteRoot : siteRoots) { 1834 cms.getRequestContext().setSiteRoot(siteRoot); 1835 for (CmsResourceTypeConfig config : getResourceTypes()) { 1836 if (!config.isDetailPagesDisabled()) { 1837 String typeName = config.getTypeName(); 1838 if (!config.isPageRelative()) { // elements stored with container pages can not be used as detail contents 1839 String folderPath = config.getFolderPath(cms, null); 1840 result.put(CmsStringUtil.joinPaths(folderPath, "/"), typeName); 1841 } 1842 } 1843 } 1844 } 1845 } else { 1846 for (CmsResourceTypeConfig config : getResourceTypes()) { 1847 if (!config.isDetailPagesDisabled()) { 1848 String typeName = config.getTypeName(); 1849 if (!config.isPageRelative()) { // elements stored with container pages can not be used as detail contents 1850 String folderPath = config.getFolderPath(getCms(), null); 1851 result.put(CmsStringUtil.joinPaths(folderPath, "/"), typeName); 1852 } 1853 } 1854 } 1855 } 1856 return result; 1857 } 1858 1859 /** 1860 * Gets the formatter configuration for a resource type.<p> 1861 * 1862 * @param cms the current CMS context 1863 * @param resType the resource type 1864 * @param schemaFormatters the resource schema formatters 1865 * 1866 * @return the configuration of formatters for the resource type 1867 */ 1868 protected CmsFormatterConfiguration getFormatters( 1869 CmsObject cms, 1870 I_CmsResourceType resType, 1871 CmsFormatterConfiguration schemaFormatters) { 1872 1873 String typeName = resType.getTypeName(); 1874 List<I_CmsFormatterBean> formatters = new ArrayList<I_CmsFormatterBean>(); 1875 Set<String> types = new HashSet<String>(); 1876 types.add(typeName); 1877 for (CmsFormatterChangeSet changeSet : getFormatterChangeSets()) { 1878 if (changeSet != null) { 1879 changeSet.applyToTypes(types); 1880 } 1881 } 1882 1883 if ((schemaFormatters != null) && types.contains(typeName)) { 1884 for (I_CmsFormatterBean formatter : schemaFormatters.getAllFormatters()) { 1885 formatters.add(formatter); 1886 } 1887 } 1888 1889 try { 1890 List<I_CmsFormatterBean> formattersForType = m_formattersByTypeCache.get(typeName); 1891 formatters.addAll(formattersForType); 1892 } catch (ExecutionException e) { 1893 LOG.error(e.getLocalizedMessage(), e); 1894 1895 } 1896 return CmsFormatterConfiguration.create(cms, formatters); 1897 } 1898 1899 /** 1900 * Gets the formatters from the schema.<p> 1901 * 1902 * @param cms the current CMS context 1903 * @param res the resource for which the formatters should be retrieved 1904 * 1905 * @return the formatters from the schema 1906 */ 1907 protected CmsFormatterConfiguration getFormattersFromSchema(CmsObject cms, CmsResource res) { 1908 1909 try { 1910 return OpenCms.getResourceManager().getResourceType(res.getTypeId()).getFormattersForResource(cms, res); 1911 } catch (CmsException e) { 1912 LOG.error(e.getLocalizedMessage(), e); 1913 return CmsFormatterConfiguration.EMPTY_CONFIGURATION; 1914 } 1915 } 1916 1917 /** 1918 * Gets the metadata about how this configuration was referenced. 1919 * 1920 * @return the metadata 1921 */ 1922 protected ConfigReferenceMeta getMeta() { 1923 1924 return m_configSequence.getMeta(); 1925 } 1926 1927 /** 1928 * Internal method for getting the function references.<p> 1929 * 1930 * @return the function references 1931 */ 1932 protected List<CmsFunctionReference> internalGetFunctionReferences() { 1933 1934 CmsADEConfigData parentData = parent(); 1935 if ((parentData == null)) { 1936 if (m_data.isModuleConfig()) { 1937 return Collections.unmodifiableList(m_data.getFunctionReferences()); 1938 } else { 1939 return Lists.newArrayList(); 1940 } 1941 } else { 1942 return parentData.internalGetFunctionReferences(); 1943 1944 } 1945 } 1946 1947 /** 1948 * Helper method for getting the list of resource types.<p> 1949 * 1950 * @param filterDisabled true if disabled types should be filtered from the result 1951 * 1952 * @return the list of resource types 1953 */ 1954 protected List<CmsResourceTypeConfig> internalGetResourceTypes(boolean filterDisabled) { 1955 1956 CmsADEConfigData parentData = parent(); 1957 List<CmsResourceTypeConfig> parentResourceTypes = null; 1958 if (parentData == null) { 1959 parentResourceTypes = Lists.newArrayList(); 1960 } else { 1961 parentResourceTypes = Lists.newArrayList(); 1962 for (CmsResourceTypeConfig typeConfig : parentData.internalGetResourceTypes(false)) { 1963 CmsResourceTypeConfig copiedType = typeConfig.copy( 1964 m_data.isDiscardInheritedTypes() && !getMeta().isSkipRemovals()); 1965 parentResourceTypes.add(copiedType); 1966 } 1967 } 1968 String template = getMeta().getTemplate(); 1969 List<CmsResourceTypeConfig> result = combineConfigurationElements( 1970 parentResourceTypes, 1971 m_data.getOwnResourceTypes().stream().map(type -> type.markWithTemplate(template)).collect( 1972 Collectors.toList()), 1973 true); 1974 if (m_data.isCreateContentsLocally()) { 1975 for (CmsResourceTypeConfig typeConfig : result) { 1976 typeConfig.updateBasePath( 1977 CmsStringUtil.joinPaths(m_data.getBasePath(), CmsADEManager.CONTENT_FOLDER_NAME)); 1978 } 1979 } 1980 if (filterDisabled) { 1981 Iterator<CmsResourceTypeConfig> iter = result.iterator(); 1982 while (iter.hasNext()) { 1983 CmsResourceTypeConfig typeConfig = iter.next(); 1984 if (typeConfig.isDisabled()) { 1985 iter.remove(); 1986 } 1987 } 1988 } 1989 if (getTypeOrderingMode() == CmsTypeOrderingMode.byDisplayOrder) { 1990 Collections.sort(result, (a, b) -> Integer.compare(a.getOrder(), b.getOrder())); 1991 } 1992 return result; 1993 } 1994 1995 /** 1996 * Merges two lists of detail pages, one from a parent configuration and one from a child configuration.<p> 1997 * 1998 * @param parentDetailPages the parent's detail pages 1999 * @param ownDetailPages the child's detail pages 2000 * 2001 * @return the merged detail pages 2002 */ 2003 protected List<CmsDetailPageInfo> mergeDetailPages( 2004 List<CmsDetailPageInfo> parentDetailPages, 2005 List<CmsDetailPageInfo> ownDetailPages) { 2006 2007 List<CmsDetailPageInfo> parentDetailPageCopies = Lists.newArrayList(); 2008 for (CmsDetailPageInfo info : parentDetailPages) { 2009 parentDetailPageCopies.add(info.copyAsInherited()); 2010 } 2011 2012 List<CmsDetailPageInfo> result = new ArrayList<CmsDetailPageInfo>(); 2013 Map<String, List<CmsDetailPageInfo>> resultDetailPageMap = Maps.newHashMap(); 2014 Map<String, List<CmsDetailPageInfo>> parentPagesGroupedByType = getDetailPagesMap(parentDetailPageCopies); 2015 Map<String, List<CmsDetailPageInfo>> childPagesGroupedByType = getDetailPagesMap(ownDetailPages); 2016 Set<String> allTypes = new HashSet<>(); 2017 allTypes.addAll(parentPagesGroupedByType.keySet()); 2018 allTypes.addAll(childPagesGroupedByType.keySet()); 2019 for (String type : allTypes) { 2020 2021 List<CmsDetailPageInfo> parentPages = parentPagesGroupedByType.get(type); 2022 List<CmsDetailPageInfo> childPages = childPagesGroupedByType.get(type); 2023 List<CmsDetailPageInfo> merged = mergeDetailPagesForType(parentPages, childPages); 2024 resultDetailPageMap.put(type, merged); 2025 } 2026 result = new ArrayList<CmsDetailPageInfo>(); 2027 for (List<CmsDetailPageInfo> pages : resultDetailPageMap.values()) { 2028 result.addAll(pages); 2029 } 2030 return result; 2031 } 2032 2033 /** 2034 * Helper method to correct paths in detail page beans if the corresponding resources have been moved.<p> 2035 * 2036 * @param detailPages the original list of detail pages 2037 * 2038 * @return the corrected list of detail pages 2039 */ 2040 protected List<CmsDetailPageInfo> updateUris(List<CmsDetailPageInfo> detailPages) { 2041 2042 List<CmsDetailPageInfo> result = new ArrayList<CmsDetailPageInfo>(); 2043 for (CmsDetailPageInfo page : detailPages) { 2044 CmsUUID structureId = page.getId(); 2045 try { 2046 String rootPath = OpenCms.getADEManager().getRootPath( 2047 structureId, 2048 getCms().getRequestContext().getCurrentProject().isOnlineProject()); 2049 String iconClasses; 2050 if (page.getType().startsWith(CmsDetailPageInfo.FUNCTION_PREFIX)) { 2051 iconClasses = CmsIconUtil.getIconClasses(CmsXmlDynamicFunctionHandler.TYPE_FUNCTION, null, false); 2052 } else { 2053 iconClasses = CmsIconUtil.getIconClasses(page.getType(), null, false); 2054 } 2055 CmsDetailPageInfo correctedPage = new CmsDetailPageInfo( 2056 structureId, 2057 rootPath, 2058 page.getType(), 2059 page.getQualifier(), 2060 iconClasses); 2061 result.add(page.isInherited() ? correctedPage.copyAsInherited() : correctedPage); 2062 } catch (CmsException e) { 2063 LOG.warn(e.getLocalizedMessage(), e); 2064 } 2065 } 2066 return result; 2067 } 2068 2069 /** 2070 * Gets a multimap of active formatters for which a formatter key is defined, with the formatter keys as map keys. 2071 * 2072 * @return the map of active formatters by key 2073 */ 2074 private Multimap<String, I_CmsFormatterBean> getActiveFormattersByKey() { 2075 2076 if (m_activeFormattersByKey == null) { 2077 ArrayListMultimap<String, I_CmsFormatterBean> activeFormattersByKey = ArrayListMultimap.create(); 2078 for (I_CmsFormatterBean formatter : getActiveFormatters().values()) { 2079 for (String key : formatter.getAllKeys()) { 2080 activeFormattersByKey.put(key, formatter); 2081 } 2082 } 2083 m_activeFormattersByKey = activeFormattersByKey; 2084 } 2085 return m_activeFormattersByKey; 2086 } 2087 2088 /** 2089 * Gets a formatter with the given key from a multimap, and warns if there are multiple values 2090 * for the key. 2091 * 2092 * @param formatterMap the formatter multimap 2093 * @param name the formatter key 2094 * @param noWarn if true, disables warnings 2095 * @return the formatter for the key (null if none are found, the first one if multiple are found) 2096 */ 2097 private I_CmsFormatterBean getFormatterAndWarnIfAmbiguous( 2098 Multimap<String, I_CmsFormatterBean> formatterMap, 2099 String name, 2100 boolean noWarn) { 2101 2102 I_CmsFormatterBean result; 2103 result = null; 2104 Collection<I_CmsFormatterBean> activeForKey = formatterMap.get(name); 2105 if (activeForKey.size() > 0) { 2106 if (activeForKey.size() > 1) { 2107 if (!noWarn) { 2108 String labels = "" 2109 + activeForKey.stream().map(this::getFormatterLabel).collect(Collectors.toList()); 2110 String message = "Ambiguous formatter for key '" 2111 + name 2112 + "' at '" 2113 + getBasePath() 2114 + "': found " 2115 + labels; 2116 LOG.warn(message); 2117 OpenCmsServlet.withRequestCache( 2118 rc -> rc.addLog(REQUEST_LOG_CHANNEL, "warn", REQ_LOG_PREFIX + message)); 2119 } 2120 } 2121 result = activeForKey.iterator().next(); 2122 } 2123 return result; 2124 } 2125 2126 /** 2127 * Gets a user-friendly formatter label to use for logging. 2128 * 2129 * @param formatter a formatter bean 2130 * @return the formatter label for the log 2131 */ 2132 private String getFormatterLabel(I_CmsFormatterBean formatter) { 2133 2134 return formatter.getLocation() != null ? formatter.getLocation() : formatter.getId(); 2135 } 2136 2137 /** 2138 * Gets formatters by JSP id. 2139 * 2140 * @return the multimap from JSP id to formatter beans 2141 */ 2142 private Multimap<CmsUUID, I_CmsFormatterBean> getFormattersByJspId() { 2143 2144 if (m_formattersByJspId == null) { 2145 ArrayListMultimap<CmsUUID, I_CmsFormatterBean> formattersByJspId = ArrayListMultimap.create(); 2146 for (I_CmsFormatterBean formatter : getCachedFormatters().getFormatters().values()) { 2147 formattersByJspId.put(formatter.getJspStructureId(), formatter); 2148 } 2149 m_formattersByJspId = formattersByJspId; 2150 } 2151 return m_formattersByJspId; 2152 } 2153 2154 /** 2155 * Gets a multimap of the formatters for which a formatter key is defined, with the formatter keys as map keys. 2156 * 2157 * @return the map of formatters by key 2158 */ 2159 private Multimap<String, I_CmsFormatterBean> getFormattersByKey() { 2160 2161 if (m_formattersByKey == null) { 2162 ArrayListMultimap<String, I_CmsFormatterBean> formattersByKey = ArrayListMultimap.create(); 2163 for (I_CmsFormatterBean formatter : getCachedFormatters().getFormatters().values()) { 2164 for (String key : formatter.getAllKeys()) { 2165 formattersByKey.put(key, formatter); 2166 } 2167 } 2168 m_formattersByKey = formattersByKey; 2169 } 2170 return m_formattersByKey; 2171 } 2172 2173 /** 2174 * Merges detail pages for a specific resource type from a parent and child sitemap. 2175 * 2176 * @param parentPages the detail pages from the parent sitemap 2177 * @param childPages the detail pages from the child sitemap 2178 * @return the merged detail pages 2179 */ 2180 private List<CmsDetailPageInfo> mergeDetailPagesForType( 2181 List<CmsDetailPageInfo> parentPages, 2182 List<CmsDetailPageInfo> childPages) { 2183 2184 List<CmsDetailPageInfo> merged = null; 2185 if ((parentPages != null) && (childPages != null)) { 2186 // the only nontrivial case. If the child detail pages contain one with an unqualified type, they completely override the parent detail pages. 2187 // otherwise they only override the parent detail pages for each matching qualifier. 2188 2189 if (childPages.stream().anyMatch(page -> page.getQualifier() == null)) { 2190 merged = childPages; 2191 } else { 2192 Map<String, List<CmsDetailPageInfo>> pagesGroupedByQualifiedType = parentPages.stream().collect( 2193 Collectors.groupingBy(page -> page.getQualifiedType())); 2194 pagesGroupedByQualifiedType.putAll( 2195 childPages.stream().collect(Collectors.groupingBy(page -> page.getQualifiedType()))); 2196 merged = pagesGroupedByQualifiedType.entrySet().stream().flatMap( 2197 entry -> entry.getValue().stream()).collect(Collectors.toList()); 2198 } 2199 } else if (parentPages != null) { 2200 merged = parentPages; 2201 } else if (childPages != null) { 2202 merged = childPages; 2203 } else { 2204 merged = new ArrayList<>(); 2205 } 2206 return merged; 2207 } 2208 2209}