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