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