001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (https://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software, please see the 018 * company website: https://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: https://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.ade.configuration.formatters; 029 030import org.opencms.gwt.shared.CmsGwtConstants; 031import org.opencms.main.CmsLog; 032import org.opencms.util.CmsUUID; 033import org.opencms.xml.content.CmsXmlContentProperty; 034 035import java.util.ArrayList; 036import java.util.Collections; 037import java.util.HashMap; 038import java.util.LinkedHashMap; 039import java.util.List; 040import java.util.Map; 041import java.util.concurrent.ExecutionException; 042 043import org.apache.commons.logging.Log; 044 045import com.google.common.cache.CacheBuilder; 046import com.google.common.cache.CacheLoader; 047import com.google.common.cache.LoadingCache; 048import com.google.common.collect.ImmutableList; 049 050/** 051 * Contains the setting-related data for a formatter bean. 052 */ 053public class CmsSettingConfiguration { 054 055 /** 056 * The shared setting configuration file that defines a setting, for configuration origin discovery: 057 * the file's structure id, whether the matching definition is formatter-specific, and whether it 058 * comes from a sitemap setting override (rather than the formatter's own shared settings). 059 */ 060 public static class CmsSettingDefiningFile { 061 062 /** The structure id of the shared setting configuration file. */ 063 private final CmsUUID m_fileId; 064 065 /** Whether the matching definition is keyed to the formatter (as opposed to global). */ 066 private final boolean m_formatterScoped; 067 068 /** Whether the file is a sitemap setting override (as opposed to a formatter shared setting). */ 069 private final boolean m_fromOverride; 070 071 /** 072 * Creates a new instance.<p> 073 * 074 * @param fileId the file structure id 075 * @param formatterScoped whether the matching definition is formatter-specific 076 * @param fromOverride whether the file is a sitemap setting override 077 */ 078 public CmsSettingDefiningFile(CmsUUID fileId, boolean formatterScoped, boolean fromOverride) { 079 080 m_fileId = fileId; 081 m_formatterScoped = formatterScoped; 082 m_fromOverride = fromOverride; 083 } 084 085 /** 086 * Gets the structure id of the defining shared setting configuration file.<p> 087 * 088 * @return the file structure id 089 */ 090 public CmsUUID getFileId() { 091 092 return m_fileId; 093 } 094 095 /** 096 * Returns whether the matching definition is keyed to the formatter (vs. the global setting).<p> 097 * 098 * @return <code>true</code> if the definition is formatter-specific 099 */ 100 public boolean isFormatterScoped() { 101 102 return m_formatterScoped; 103 } 104 105 /** 106 * Returns whether the file is a sitemap setting override (vs. a formatter shared setting).<p> 107 * 108 * @return <code>true</code> if the file is a sitemap setting override 109 */ 110 public boolean isFromOverride() { 111 112 return m_fromOverride; 113 } 114 } 115 116 /** The logger instance for this class. */ 117 private static final Log LOG = CmsLog.getLog(CmsSettingConfiguration.class); 118 119 /** Cache for calculating and storing the setting definition maps for various combinations of override shared setting configuration file ids. */ 120 private LoadingCache<ImmutableList<CmsUUID>, Map<String, CmsXmlContentProperty>> m_cache = CacheBuilder.newBuilder().concurrencyLevel( 121 4).build(new CacheLoader<ImmutableList<CmsUUID>, Map<String, CmsXmlContentProperty>>() { 122 123 @SuppressWarnings("synthetic-access") 124 @Override 125 public Map<String, CmsXmlContentProperty> load(ImmutableList<CmsUUID> sharedSettingOverrides) 126 throws Exception { 127 128 return resolveSettings(sharedSettingOverrides); 129 130 } 131 132 }); 133 134 /** The display type. */ 135 private String m_displayType; 136 137 /** The key of the formatter using this configuration (may be null). */ 138 private String m_formatterKey; 139 140 /** The settings configured in the formatter configuration. */ 141 private List<CmsXmlContentProperty> m_listedSettings; 142 143 /** A map of all shared setting configurations in the system, with their structure ids as keys. */ 144 private Map<CmsUUID, Map<CmsSharedSettingKey, CmsXmlContentProperty>> m_sharedSettingConfigsById; 145 146 /** The list of structure ids of shared settings files configured in the formatter. The last entry has the highest priority. */ 147 private List<CmsUUID> m_sharedSettingsIdsFromFormatter; 148 149 /** 150 * Creates an empty configuration. 151 */ 152 public CmsSettingConfiguration() { 153 154 m_listedSettings = new ArrayList<>(); 155 m_sharedSettingConfigsById = new HashMap<>(); 156 m_displayType = null; 157 m_sharedSettingsIdsFromFormatter = new ArrayList<>(); 158 } 159 160 /** 161 * 162 * @param listedSettings the setting entries configured in the formatter configuration 163 * @param sharedSettingConfigsById the map of shared setting configurations, with their structure ids as keys 164 * @param includeIds the list of structure ids of shared setting configurations referenced from the formatter configuration 165 * @param formatterKey the key of the formatter using this setting configuration (may be null) 166 * @param displayType the display type 167 */ 168 public CmsSettingConfiguration( 169 List<CmsXmlContentProperty> listedSettings, 170 Map<CmsUUID, Map<CmsSharedSettingKey, CmsXmlContentProperty>> sharedSettingConfigsById, 171 List<CmsUUID> includeIds, 172 String formatterKey, 173 String displayType) { 174 175 m_listedSettings = listedSettings; 176 m_sharedSettingConfigsById = sharedSettingConfigsById; 177 m_displayType = displayType; 178 m_formatterKey = formatterKey; 179 m_sharedSettingsIdsFromFormatter = new ArrayList<>(includeIds); 180 } 181 182 /** 183 * Finds the shared setting configuration file that defines the given setting, searching the 184 * sitemap setting overrides first (highest priority) and then the formatter's own shared setting 185 * files, in order of decreasing specificity. Read-only, used for configuration origin discovery.<p> 186 * 187 * @param overrideIds the sitemap setting override file ids in ascending specificity, e.g. from 188 * {@link org.opencms.ade.configuration.CmsADEConfigData#getSharedSettingOverrides()} 189 * @param settingName the setting name (the property name of the setting) 190 * 191 * @return the file defining the setting, or <code>null</code> if no shared setting file defines it 192 */ 193 public CmsSettingDefiningFile findDefiningFile(List<CmsUUID> overrideIds, String settingName) { 194 195 // shared settings are keyed by include name, so resolve the requested setting name back to the 196 // include name the formatter actually references before searching the files 197 String includeName = resolveIncludeName(overrideIds, settingName); 198 // sitemap overrides win over the formatter's own shared settings; within each group the last 199 // entry is the most specific, so search from the most specific downwards 200 CmsSettingDefiningFile fromOverride = findInFiles(overrideIds, includeName, true); 201 if (fromOverride != null) { 202 return fromOverride; 203 } 204 return findInFiles(m_sharedSettingsIdsFromFormatter, includeName, false); 205 } 206 207 /** 208 * Gets the setting map by looking up the configured settings' include names in either the shared settings files 209 * configured in the formatter configuration, or the override shared settings files whose ids are passed as parameter. 210 * 211 * <p> 212 * Setting definitions from Override shared settings files have higher priority than those referenced in the formatter 213 * configuration, and later entries in both lists have higher prioritiy than earlier ones. 214 * 215 * @param sharedSettingOverrides the structure ids of Override shared setting configurations (highest priority last) 216 * 217 * @return the setting definition map 218 */ 219 public Map<String, CmsXmlContentProperty> getSettings(ImmutableList<CmsUUID> sharedSettingOverrides) { 220 221 try { 222 return m_cache.get(sharedSettingOverrides); 223 } catch (ExecutionException e) { 224 LOG.error(e.getLocalizedMessage(), e); 225 return Collections.emptyMap(); 226 } 227 } 228 229 /** 230 * Combines shard setting definitions from multiple shared setting files into a single map. 231 * 232 * @param ids the structure ids of shared setting files, in order of increasing specificity 233 * 234 * @return the combined map of shared setting definitions 235 */ 236 private Map<CmsSharedSettingKey, CmsXmlContentProperty> combineSharedSettingDefinitionMaps(List<CmsUUID> ids) { 237 238 Map<CmsSharedSettingKey, CmsXmlContentProperty> result = new HashMap<>(); 239 for (CmsUUID settingFileId : ids) { 240 Map<CmsSharedSettingKey, CmsXmlContentProperty> sharedSettingsForLevel = m_sharedSettingConfigsById.get( 241 settingFileId); 242 if (sharedSettingsForLevel != null) { 243 // since we have different map keys for each (includeName, formatterKey) combination, 244 // putAll does the right thing here, i.e. setting definitions are overridden for their individual formatter keys 245 result.putAll(sharedSettingsForLevel); 246 } else { 247 LOG.warn("Shared setting reference not found: " + settingFileId); 248 } 249 } 250 return result; 251 } 252 253 /** 254 * Searches a list of shared setting files (in ascending specificity) for a definition with the 255 * given include name, most specific file first, preferring a formatter-specific entry over the 256 * global one.<p> 257 * 258 * @param ids the shared setting file ids in ascending specificity 259 * @param includeName the include name of the setting to look for 260 * @param fromOverride whether the files are sitemap setting overrides 261 * 262 * @return the defining file, or <code>null</code> 263 */ 264 private CmsSettingDefiningFile findInFiles(List<CmsUUID> ids, String includeName, boolean fromOverride) { 265 266 for (int i = ids.size() - 1; i >= 0; i--) { 267 Map<CmsSharedSettingKey, CmsXmlContentProperty> map = m_sharedSettingConfigsById.get(ids.get(i)); 268 if (map == null) { 269 continue; 270 } 271 // the shared setting maps are keyed by (include name, formatter key); match the resolved 272 // include name and prefer a formatter-specific entry over the global one 273 if ((m_formatterKey != null) && map.containsKey(new CmsSharedSettingKey(includeName, m_formatterKey))) { 274 return new CmsSettingDefiningFile(ids.get(i), true, fromOverride); 275 } 276 if (map.containsKey(new CmsSharedSettingKey(includeName, null))) { 277 return new CmsSettingDefiningFile(ids.get(i), false, fromOverride); 278 } 279 } 280 return null; 281 } 282 283 /** 284 * Helper method to get a shared setting for this formatter. 285 * 286 * <p>Prioritizes shared settings with a formatter key matching this formatter's key. 287 * 288 * @param map the map of shared settings 289 * @param includeName the effective include name of the setting definition to find 290 * 291 * @return the shared setting definition 292 */ 293 private CmsXmlContentProperty getSharedSetting( 294 Map<CmsSharedSettingKey, CmsXmlContentProperty> map, 295 String includeName) { 296 297 CmsXmlContentProperty result = null; 298 299 // try formatter key specific entry first, if not found try the general entry 300 301 if (m_formatterKey != null) { 302 result = map.get(new CmsSharedSettingKey(includeName, m_formatterKey)); 303 } 304 if (result == null) { 305 result = map.get(new CmsSharedSettingKey(includeName, null)); 306 } 307 return result; 308 309 } 310 311 /** 312 * Merges a setting listed in the formatter with its matching shared and override definitions, pulling 313 * each field value from the first of [override, formatter, shared] where it is defined. 314 * 315 * @param settingDef the setting definition listed in the formatter 316 * @param includeName the effective include name of the setting definition 317 * @param sharedSettingDefinitions the shared setting definitions referenced from the formatter 318 * @param overrideSettingDefinitions the sitemap override setting definitions active in the current context 319 * 320 * @return the merged setting definition 321 */ 322 private CmsXmlContentProperty mergeListedSetting( 323 CmsXmlContentProperty settingDef, 324 String includeName, 325 Map<CmsSharedSettingKey, CmsXmlContentProperty> sharedSettingDefinitions, 326 Map<CmsSharedSettingKey, CmsXmlContentProperty> overrideSettingDefinitions) { 327 328 CmsXmlContentProperty defaultSetting = getSharedSetting(sharedSettingDefinitions, includeName); 329 CmsXmlContentProperty overrideSetting = getSharedSetting(overrideSettingDefinitions, includeName); 330 CmsXmlContentProperty mergedSetting = settingDef; 331 if (defaultSetting != null) { 332 mergedSetting = mergedSetting.mergeDefaults(defaultSetting); 333 } 334 if (overrideSetting != null) { 335 mergedSetting = overrideSetting.mergeDefaults(mergedSetting); 336 } 337 return mergedSetting; 338 } 339 340 /** 341 * Resolves a setting's effective (property) name back to the include name that the formatter 342 * references it by, mirroring how {@link #resolveSettings} derives the effective settings. Read-only, 343 * used for configuration origin discovery.<p> 344 * 345 * @param overrideIds the sitemap setting override file ids active in the current context 346 * @param settingName the effective (property) name of the setting 347 * 348 * @return the include name the formatter references the setting by, or <code>settingName</code> if no 349 * listed setting resolves to it 350 */ 351 private String resolveIncludeName(List<CmsUUID> overrideIds, String settingName) { 352 353 Map<CmsSharedSettingKey, CmsXmlContentProperty> sharedSettingDefinitions = combineSharedSettingDefinitionMaps( 354 m_sharedSettingsIdsFromFormatter); 355 Map<CmsSharedSettingKey, CmsXmlContentProperty> overrideSettingDefinitions = combineSharedSettingDefinitionMaps( 356 overrideIds); 357 String result = settingName; 358 // later listed settings win, mirroring the last-wins put in resolveSettings 359 for (CmsXmlContentProperty settingDef : m_listedSettings) { 360 String includeName = settingDef.getIncludeName(settingDef.getName()); 361 if (includeName == null) { 362 continue; 363 } 364 CmsXmlContentProperty mergedSetting = mergeListedSetting( 365 settingDef, 366 includeName, 367 sharedSettingDefinitions, 368 overrideSettingDefinitions); 369 if (settingName.equals(mergedSetting.getName())) { 370 result = includeName; 371 } 372 } 373 return result; 374 } 375 376 /** 377 * Computes the finished map of settings for the given combination of shared setting overrides.+ 378 * 379 * @param overrideSharedSettingsIds the structure ids of shared setting overrides active in the current context, with the most specific override last 380 * 381 * @return the finished map of settings 382 */ 383 private Map<String, CmsXmlContentProperty> resolveSettings(ImmutableList<CmsUUID> overrideSharedSettingsIds) { 384 385 Map<String, CmsXmlContentProperty> result = new LinkedHashMap<>(); 386 387 Map<CmsSharedSettingKey, CmsXmlContentProperty> sharedSettingDefinitions = combineSharedSettingDefinitionMaps( 388 m_sharedSettingsIdsFromFormatter); 389 Map<CmsSharedSettingKey, CmsXmlContentProperty> overrideSettingDefinitions = combineSharedSettingDefinitionMaps( 390 overrideSharedSettingsIds); 391 392 /* 393 * For each setting listed in the formatter, try to find a matching setting definition in both the shared settings (referenced from the formatter) 394 * and the setting overrides (configured in the sitemap/master configuration). These three setting definition objects (or less, if the setting override or shared 395 * setting doesn't exist) are then merged, such that each individual field value for the setting definition is pulled from the first entry in the list 396 * [overrideSetting, settingFromFormatter, settingFromSharedSettings] where it is defined. I.e. the field values defined in setting overrides have the highest 397 * priority. 398 */ 399 for (CmsXmlContentProperty settingDef : m_listedSettings) { 400 String includeName = settingDef.getIncludeName(settingDef.getName()); 401 if (includeName == null) { 402 continue; 403 } 404 405 CmsXmlContentProperty mergedSetting = mergeListedSetting( 406 settingDef, 407 includeName, 408 sharedSettingDefinitions, 409 overrideSettingDefinitions); 410 if (mergedSetting.getName() == null) { 411 continue; 412 } 413 result.put(mergedSetting.getName(), mergedSetting); 414 } 415 if ((m_displayType != null) && !result.containsKey(CmsFormatterBeanParser.SETTING_DISPLAY_TYPE)) { 416 CmsXmlContentProperty displayType = new CmsXmlContentProperty( 417 CmsFormatterBeanParser.SETTING_DISPLAY_TYPE, 418 "string", 419 CmsGwtConstants.HIDDEN_SETTINGS_WIDGET_NAME, 420 null, 421 null, 422 null, 423 m_displayType, 424 null, 425 null, 426 null, 427 null); 428 result.put(displayType.getName(), displayType); 429 } 430 return Collections.unmodifiableMap(result); 431 } 432}