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.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    /** The logger instance for this class. */
056    private static final Log LOG = CmsLog.getLog(CmsSettingConfiguration.class);
057
058    /** Cache for calculating and storing the setting definition maps for various combinations of override shared setting configuration file ids. */
059    private LoadingCache<ImmutableList<CmsUUID>, Map<String, CmsXmlContentProperty>> m_cache = CacheBuilder.newBuilder().concurrencyLevel(
060        4).build(new CacheLoader<ImmutableList<CmsUUID>, Map<String, CmsXmlContentProperty>>() {
061
062            @SuppressWarnings("synthetic-access")
063            @Override
064            public Map<String, CmsXmlContentProperty> load(ImmutableList<CmsUUID> sharedSettingOverrides)
065            throws Exception {
066
067                return resolveSettings(sharedSettingOverrides);
068
069            }
070
071        });
072
073    /** The display type. */
074    private String m_displayType;
075
076    /** The key of the formatter using this configuration (may be null). */
077    private String m_formatterKey;
078
079    /** The settings configured in the formatter configuration. */
080    private List<CmsXmlContentProperty> m_listedSettings;
081
082    /** A map of all shared setting configurations in the system, with their structure ids as keys. */
083    private Map<CmsUUID, Map<CmsSharedSettingKey, CmsXmlContentProperty>> m_sharedSettingConfigsById;
084
085    /** The list of structure ids of shared settings files configured in the formatter. The last entry has the highest priority. */
086    private List<CmsUUID> m_sharedSettingsIdsFromFormatter;
087
088    /**
089     * Creates an empty configuration.
090     */
091    public CmsSettingConfiguration() {
092
093        m_listedSettings = new ArrayList<>();
094        m_sharedSettingConfigsById = new HashMap<>();
095        m_displayType = null;
096        m_sharedSettingsIdsFromFormatter = new ArrayList<>();
097    }
098
099    /**
100     *
101     * @param listedSettings  the setting entries configured in the formatter configuration
102     * @param sharedSettingConfigsById the map of shared setting configurations, with their structure ids as keys
103     * @param includeIds the list of structure ids of shared setting configurations referenced from the formatter configuration
104     * @param formatterKey the key of the formatter using this setting configuration (may be null)
105     * @param displayType the display type
106     */
107    public CmsSettingConfiguration(
108        List<CmsXmlContentProperty> listedSettings,
109        Map<CmsUUID, Map<CmsSharedSettingKey, CmsXmlContentProperty>> sharedSettingConfigsById,
110        List<CmsUUID> includeIds,
111        String formatterKey,
112        String displayType) {
113
114        m_listedSettings = listedSettings;
115        m_sharedSettingConfigsById = sharedSettingConfigsById;
116        m_displayType = displayType;
117        m_formatterKey = formatterKey;
118        m_sharedSettingsIdsFromFormatter = new ArrayList<>(includeIds);
119    }
120
121    /**
122     * Gets the setting map by looking up the configured settings' include names in either the shared settings files
123     * configured in the formatter configuration, or the override shared settings files whose ids are passed as parameter.
124     *
125     *  <p>
126     *  Setting definitions from Override shared settings files have higher priority than those referenced in the formatter
127     *  configuration, and later entries in both lists have higher prioritiy than earlier ones.
128     *
129     * @param sharedSettingOverrides the structure ids of Override shared setting configurations (highest priority last)
130     *
131     * @return the setting definition map
132     */
133    public Map<String, CmsXmlContentProperty> getSettings(ImmutableList<CmsUUID> sharedSettingOverrides) {
134
135        try {
136            return m_cache.get(sharedSettingOverrides);
137        } catch (ExecutionException e) {
138            LOG.error(e.getLocalizedMessage(), e);
139            return Collections.emptyMap();
140        }
141    }
142
143    /**
144     * Combines shard setting definitions from multiple shared setting files into a single map.
145     *
146     * @param ids the structure ids of shared setting files, in order of increasing specificity
147     *
148     * @return the combined map of shared setting definitions
149     */
150    private Map<CmsSharedSettingKey, CmsXmlContentProperty> combineSharedSettingDefinitionMaps(List<CmsUUID> ids) {
151
152        Map<CmsSharedSettingKey, CmsXmlContentProperty> result = new HashMap<>();
153        for (CmsUUID settingFileId : ids) {
154            Map<CmsSharedSettingKey, CmsXmlContentProperty> sharedSettingsForLevel = m_sharedSettingConfigsById.get(
155                settingFileId);
156            if (sharedSettingsForLevel != null) {
157                // since we have different map keys for each (includeName, formatterKey) combination,
158                // putAll does the right thing here, i.e. setting definitions are overridden for their individual formatter keys
159                result.putAll(sharedSettingsForLevel);
160            } else {
161                LOG.warn("Shared setting reference not found: " + settingFileId);
162            }
163        }
164        return result;
165    }
166
167    /**
168     * Helper method to get a shared setting for this formatter.
169     *
170     *  <p>Prioritizes shared settings with a formatter key matching this formatter's key.
171     *
172     * @param map the map of shared settings
173     * @param includeName the effective include name of the setting definition to find
174     *
175     * @return the shared setting definition
176     */
177    private CmsXmlContentProperty getSharedSetting(
178        Map<CmsSharedSettingKey, CmsXmlContentProperty> map,
179        String includeName) {
180
181        CmsXmlContentProperty result = null;
182
183        // try formatter key specific entry first, if not found try the general entry
184
185        if (m_formatterKey != null) {
186            result = map.get(new CmsSharedSettingKey(includeName, m_formatterKey));
187        }
188        if (result == null) {
189            result = map.get(new CmsSharedSettingKey(includeName, null));
190        }
191        return result;
192
193    }
194
195    /**
196     * Computes the finished map of settings for the given combination of shared setting overrides.+
197     *
198     * @param overrideSharedSettingsIds the structure ids of shared setting overrides active in the current context, with the most specific override last
199     *
200     * @return the finished map of settings
201     */
202    private Map<String, CmsXmlContentProperty> resolveSettings(ImmutableList<CmsUUID> overrideSharedSettingsIds) {
203
204        Map<String, CmsXmlContentProperty> result = new LinkedHashMap<>();
205
206        Map<CmsSharedSettingKey, CmsXmlContentProperty> sharedSettingDefinitions = combineSharedSettingDefinitionMaps(
207            m_sharedSettingsIdsFromFormatter);
208        Map<CmsSharedSettingKey, CmsXmlContentProperty> overrideSettingDefinitions = combineSharedSettingDefinitionMaps(
209            overrideSharedSettingsIds);
210
211        /*
212         * For each setting listed in the formatter, try to find a matching setting definition in both the shared settings (referenced from the formatter)
213         * and the setting overrides (configured in the sitemap/master configuration). These three setting definition objects (or less, if the setting override or shared
214         * 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
215         * [overrideSetting, settingFromFormatter, settingFromSharedSettings] where it is defined. I.e. the field values defined in setting overrides have the highest
216         * priority.
217         */
218        for (CmsXmlContentProperty settingDef : m_listedSettings) {
219            String includeName = settingDef.getIncludeName(settingDef.getName());
220            if (includeName == null) {
221                continue;
222            }
223
224            CmsXmlContentProperty defaultSetting = getSharedSetting(sharedSettingDefinitions, includeName);
225            CmsXmlContentProperty overrideSetting = getSharedSetting(overrideSettingDefinitions, includeName);
226            CmsXmlContentProperty mergedSetting = settingDef;
227            if (defaultSetting != null) {
228                mergedSetting = mergedSetting.mergeDefaults(defaultSetting);
229            }
230            if (overrideSetting != null) {
231                mergedSetting = overrideSetting.mergeDefaults(mergedSetting);
232            }
233            if (mergedSetting.getName() == null) {
234                continue;
235            }
236            result.put(mergedSetting.getName(), mergedSetting);
237        }
238        if ((m_displayType != null) && !result.containsKey(CmsFormatterBeanParser.SETTING_DISPLAY_TYPE)) {
239            CmsXmlContentProperty displayType = new CmsXmlContentProperty(
240                CmsFormatterBeanParser.SETTING_DISPLAY_TYPE,
241                "string",
242                CmsGwtConstants.HIDDEN_SETTINGS_WIDGET_NAME,
243                null,
244                null,
245                null,
246                m_displayType,
247                null,
248                null,
249                null,
250                null);
251            result.put(displayType.getName(), displayType);
252        }
253        return Collections.unmodifiableMap(result);
254    }
255}