001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (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.containerpage; 029 030import org.opencms.ade.configuration.CmsADEConfigData; 031import org.opencms.main.CmsLog; 032import org.opencms.util.CmsStringUtil; 033import org.opencms.xml.containerpage.I_CmsFormatterBean; 034import org.opencms.xml.content.CmsXmlContentProperty; 035 036import java.util.ArrayList; 037import java.util.Collections; 038import java.util.HashMap; 039import java.util.List; 040import java.util.Map; 041 042import org.apache.commons.logging.Log; 043 044/** 045 * Helper class for transforming a map of element settings based on the aliases/replacement rules in the setting definitions for a given formatter. 046 * <p> 047 * The translation method creates a new setting map according to the following rules: 048 * <ul> 049 * <li>If the setting map contains an entry whose key is the alias of a setting definition, the key is changed to the current setting name. 050 * <li>If the setting map contains an entry whose key has the form ${formatterKey}_${settingAlias}, where ${settingAlias} is an alias of a setting with name ${newSetting} for the formatter 051 * with key ${formatterKey}, the map key is replaced by ${formatterKey}_${newSetting} (these are nested settings used for list configuration elements) 052 * <li>If the setting definition for an entry contains a value translation of the form 'newval_1:oldval_1|newval_2:oldval_2|....', and the value of the entry is one of the oldval_i values in that list, 053 * the value will be replaced by the corresponding newval_i value. 054 * <li>If none of these are the case, the setting entry will be copied as-is. 055 * </ul> 056 * 057 * <p> 058 * A single setting translator instance is used for processing the element settings of a single container page while it is being unmarshalled. 059 * 060 */ 061public class CmsSettingTranslator { 062 063 /** Logger instance for this class. */ 064 private static final Log LOG = CmsLog.getLog(CmsSettingTranslator.class); 065 066 /** The active sitemap configuration. */ 067 private CmsADEConfigData m_config; 068 069 /** A cache containing settings maps for various formatter keys. */ 070 private HashMap<String, Map<String, CmsXmlContentProperty>> m_settingsCache = new HashMap<>(); 071 072 /** A cache map whose keys are textual representations of setting value translations, and the values are the corresponding maps mapping old values to new values.*/ 073 private Map<String, Map<String, String>> m_translationMapCache = new HashMap<>(); 074 075 /** 076 * Creates a new instance. 077 * 078 * @param config the active sitemap configuration 079 */ 080 public CmsSettingTranslator(CmsADEConfigData config) { 081 082 if (config == null) { 083 throw new IllegalArgumentException("Sitemap configuration must not be null."); 084 } 085 m_config = config; 086 } 087 088 /** 089 * Parses a setting value translation of the form newval1:oldval1|newval2:oldval2|.... . 090 * 091 * @param translation the setting value translation 092 * @return the setting translation map, with the old values as keys and the corresponding new values as values 093 */ 094 public static Map<String, String> parseSettingTranslationMap(String translation) { 095 096 if (translation == null) { 097 return null; 098 } 099 Map<String, String> result = new HashMap<>(); 100 String[] parts = translation.split("\\|"); 101 for (String part : parts) { 102 int colonPos = part.indexOf(":"); 103 if (colonPos != -1) { 104 String left = part.substring(0, colonPos); 105 left = left.trim(); 106 String right = part.substring(colonPos + 1); 107 right = right.trim(); 108 result.put(right, left); 109 } 110 } 111 return result; 112 } 113 114 /** 115 * Translates the settings for the given formatter in the context of the current sitemap. 116 * 117 * @param formatter the formatter 118 * @param settings the settings to translate 119 * @return the map of translated settings 120 */ 121 public Map<String, String> translateSettings(I_CmsFormatterBean formatter, Map<String, String> settings) { 122 123 Map<String, String> result = new HashMap<>(); 124 Map<String, CmsXmlContentProperty> settingDefsWithAliases = getSettingsForFormatter(formatter.getKeyOrId()); 125 for (Map.Entry<String, String> entry : settings.entrySet()) { 126 String key = entry.getKey(); 127 String value = entry.getValue(); 128 String targetKey = key; 129 String targetValue = value; 130 int underscorePos = key.indexOf("_"); 131 CmsXmlContentProperty matchingSetting = settingDefsWithAliases.get(key); 132 if (matchingSetting != null) { 133 targetKey = matchingSetting.getName(); 134 targetValue = translateValue(matchingSetting, value); 135 } else if (underscorePos != -1) { 136 String beforeUnderscore = key.substring(0, underscorePos); 137 String afterUnderscore = key.substring(underscorePos + 1); 138 CmsXmlContentProperty nestedSetting = getSettingsForFormatter(beforeUnderscore).get(afterUnderscore); 139 if (nestedSetting != null) { 140 targetKey = beforeUnderscore + "_" + nestedSetting.getName(); // this only makes a difference if the alias name matched 141 targetValue = translateValue(nestedSetting, value); 142 } 143 } 144 result.put(targetKey, targetValue); 145 } 146 return result; 147 } 148 149 /** 150 * Helper method to get the map of settings for a given formatter key, where setting name aliases can also be used as keys. 151 * 152 * @param formatterKey the key of a formatter 153 * @return the map of settings for the formatter key 154 */ 155 protected Map<String, CmsXmlContentProperty> getSettingsForFormatter(String formatterKey) { 156 157 return m_settingsCache.computeIfAbsent(formatterKey, k -> { 158 159 I_CmsFormatterBean formatter = m_config.findFormatter(k, /*noWarn=*/true); 160 if (formatter != null) { 161 // build map for lookup via name or alias 162 Map<String, CmsXmlContentProperty> settings = formatter.getSettings(m_config); 163 Map<String, CmsXmlContentProperty> result = new HashMap<>(); 164 for (CmsXmlContentProperty settingDef : settings.values()) { 165 List<String> mapKeys = new ArrayList<>(); 166 mapKeys.add(settingDef.getName()); 167 if (settingDef.getAliasName() != null) { 168 for (String item : CmsStringUtil.splitAsList(settingDef.getAliasName(), "|")) { 169 mapKeys.add(item.trim()); 170 } 171 } 172 for (String mapKey : mapKeys) { 173 if (mapKey != null) { 174 if (null != result.put(mapKey, settingDef)) { 175 LOG.warn( 176 "Setting name collision for formatter " + formatterKey + ", setting " + mapKey); 177 } 178 } 179 } 180 } 181 return result; 182 } else { 183 return Collections.emptyMap(); 184 } 185 186 }); 187 } 188 189 /** 190 * Translates a single setting value. 191 * 192 * @param settingDef the setting whose value should be translated 193 * @param value the value to translate 194 * @return the translated value 195 */ 196 private String translateValue(CmsXmlContentProperty settingDef, String value) { 197 198 if (settingDef.getTranslationStr() == null) { 199 return value; 200 } 201 Map<String, String> translationMap = m_translationMapCache.computeIfAbsent( 202 settingDef.getTranslationStr(), 203 translationStr -> parseSettingTranslationMap(translationStr)); 204 String mappedValue = translationMap.get(value); 205 if (mappedValue != null) { 206 return mappedValue; 207 } else { 208 return value; 209 } 210 211 } 212}