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}