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.xml.templatemapper;
029
030import org.opencms.ade.containerpage.shared.CmsFormatterConfig;
031import org.opencms.cache.CmsVfsMemoryObjectCache;
032import org.opencms.file.CmsFile;
033import org.opencms.file.CmsObject;
034import org.opencms.file.CmsResourceFilter;
035import org.opencms.gwt.shared.CmsTemplateContextInfo;
036import org.opencms.loader.CmsTemplateContext;
037import org.opencms.loader.CmsTemplateContextManager;
038import org.opencms.loader.I_CmsTemplateContextProvider;
039import org.opencms.main.CmsException;
040import org.opencms.main.CmsLog;
041import org.opencms.main.OpenCms;
042import org.opencms.util.CmsUUID;
043import org.opencms.xml.containerpage.CmsContainerBean;
044import org.opencms.xml.containerpage.CmsContainerElementBean;
045import org.opencms.xml.containerpage.CmsContainerPageBean;
046import org.opencms.xml.containerpage.CmsGroupContainerBean;
047
048import java.io.ByteArrayInputStream;
049import java.util.ArrayList;
050import java.util.HashMap;
051import java.util.HashSet;
052import java.util.List;
053import java.util.Map;
054import java.util.Set;
055
056import javax.servlet.ServletRequest;
057
058import org.apache.commons.collections.Transformer;
059import org.apache.commons.logging.Log;
060
061import org.dom4j.Document;
062import org.dom4j.io.SAXReader;
063
064/**
065 * Responsible for mapping formatters, containers and settings to different formatters, containers and settings according to
066 * the configuration file /system/config/template-mapping.xml.<p>
067 *
068 */
069public final class CmsTemplateMapper {
070
071    /** The logger instance for this class. */
072    static final Log LOG = CmsLog.getLog(CmsTemplateMapper.class);
073
074    /** Flag which controls whether this is enabled. */
075    protected boolean m_enabled;
076
077    /** The path to the mapper configuration. */
078    protected String m_configPath;
079
080    /** Flag to enable mode for saving. */
081    private boolean m_forSave;
082
083    /**
084     * Creates a new instance.<p>
085     *
086     * @param configPath the template mapper configuration VFS path
087     */
088    public CmsTemplateMapper(String configPath) {
089
090        if (configPath != null) {
091            m_enabled = true;
092            m_configPath = configPath;
093        } else {
094            m_enabled = false;
095        }
096    }
097
098    /**
099     * Hidden default constructor, because this is a singleton.<p>
100     */
101    private CmsTemplateMapper() {
102
103        m_enabled = true;
104    }
105
106    /**
107     * Gets a template mapper.
108     *
109     * @return a template mapper
110     */
111    public static CmsTemplateMapper get() {
112
113        return new CmsTemplateMapper();
114    }
115
116    /**
117     * Gets the template mapper for the current request.<p>
118     *
119     * @param request the current request
120     *
121     * @return the template mapper
122     */
123    public static CmsTemplateMapper get(ServletRequest request) {
124
125        return new CmsTemplateMapper(getTemplateMapperConfig(request));
126    }
127
128    /**
129     * Checks if the selected template context is "templatemapper".
130     *
131     * @param request the current request
132     * @return true if the selected template context is "templatemapper"
133     */
134    public static String getTemplateMapperConfig(ServletRequest request) {
135
136        String result = null;
137        CmsTemplateContext templateContext = (CmsTemplateContext)request.getAttribute(
138            CmsTemplateContextManager.ATTR_TEMPLATE_CONTEXT);
139        if (templateContext != null) {
140            I_CmsTemplateContextProvider provider = templateContext.getProvider();
141            if (provider instanceof I_CmsTemplateMappingContextProvider) {
142                result = ((I_CmsTemplateMappingContextProvider)provider).getMappingConfigurationPath(
143                    templateContext.getKey());
144            }
145        }
146        return result;
147    }
148
149    /**
150     * Sets the for-save mode.<p>
151     *
152     * @param forSave true if for-save mode should be enabled
153     */
154    public void setForSave(boolean forSave) {
155
156        m_forSave = forSave;
157    }
158
159    /**
160     * Transforms a container page bean.<p>
161     *
162     * @param cms the current CMS context
163     * @param input the bean to be transformed
164     * @param rootPath the root path of the page
165     *
166     * @return the transformed bean
167     */
168    public CmsContainerPageBean transformContainerpageBean(CmsObject cms, CmsContainerPageBean input, String rootPath) {
169
170        CmsTemplateMapperConfiguration config = getConfiguration(cms);
171        if ((config == null) || !config.isEnabledForPath(rootPath)) {
172            return input;
173        }
174        List<CmsContainerBean> newContainers = new ArrayList<>();
175        for (CmsContainerBean container : input.getContainers().values()) {
176            List<CmsContainerElementBean> elements = container.getElements();
177            List<CmsContainerElementBean> newElements = new ArrayList<>();
178            for (CmsContainerElementBean element : elements) {
179                CmsContainerElementBean newElement = transformContainerElement(cms, config, element);
180                if (newElement != null) {
181                    newElements.add(newElement);
182                }
183            }
184            CmsContainerBean newContainer = container.copyWithNewElements(newElements);
185            newContainers.add(newContainer);
186        }
187        CmsContainerPageBean result = new CmsContainerPageBean(newContainers);
188        return result;
189    }
190
191    /**
192     * Transforms a container element bean used for detail elements.<p>
193     *
194     * @param cms the current CMS context
195     * @param input the bean to be transformed
196     * @param rootPath the root path of the page
197     *
198     * @return the transformed bean
199     */
200    public CmsContainerElementBean transformDetailElement(
201        CmsObject cms,
202        CmsContainerElementBean input,
203        String rootPath) {
204
205        CmsTemplateMapperConfiguration config = getConfiguration(cms);
206        if ((config == null) || !config.isEnabledForPath(rootPath)) {
207            return input;
208        }
209        return transformContainerElement(cms, config, input);
210
211    }
212
213    /**
214     * Transforms a group container bean.<p>
215     *
216     * @param cms the current CMS context
217     * @param input the input bean to be transformed
218     * @param rootPath the root path of the container page
219     *
220     * @return the transformed bean
221     */
222    public CmsGroupContainerBean transformGroupContainer(CmsObject cms, CmsGroupContainerBean input, String rootPath) {
223
224        CmsTemplateMapperConfiguration config = getConfiguration(cms);
225        if ((config == null) || !config.isEnabledForPath(rootPath)) {
226            return input;
227        }
228        List<CmsContainerElementBean> newElements = new ArrayList<>();
229        for (CmsContainerElementBean element : input.getElements()) {
230            CmsContainerElementBean newElement = transformContainerElement(cms, config, element);
231            if (newElement != null) {
232                newElements.add(newElement);
233            }
234        }
235        Set<String> transformedTypes = new HashSet<>();
236        Set<String> oldTypes = input.getTypes();
237        if (oldTypes == null) {
238            oldTypes = new HashSet<>();
239        }
240        for (String type : oldTypes) {
241            String newType = config.getMappedElementGroupType(type);
242            if (newType == null) {
243                newType = type;
244            }
245            transformedTypes.add(newType);
246        }
247
248        CmsGroupContainerBean result = new CmsGroupContainerBean(
249            input.getTitle(),
250            input.getDescription(),
251            newElements,
252            transformedTypes);
253        return result;
254    }
255
256    /**
257     * Helper method to transform a single container element.<p>
258     * @param cms the CMS context
259     * @param config the configuration
260     * @param element the container element to be transformed
261     *
262     * @return the transformed bean
263     */
264    protected CmsContainerElementBean transformContainerElement(
265        CmsObject cms,
266        CmsTemplateMapperConfiguration config,
267        CmsContainerElementBean element) {
268
269        if (m_forSave) {
270            try {
271                element.initResource(cms);
272            } catch (Exception e) {
273                LOG.error(e.getLocalizedMessage(), e);
274                return null;
275            }
276        }
277        Map<String, String> settings = element.getIndividualSettings();
278        if (settings == null) {
279            settings = new HashMap<>();
280        }
281        Map<String, String> newSettings = new HashMap<>();
282        for (Map.Entry<String, String> entry : settings.entrySet()) {
283            String key = entry.getKey();
284            if (CmsTemplateContextInfo.SETTING.equals(key)) {
285                continue;
286            }
287            String value = entry.getValue();
288            if (value == null) {
289                continue;
290            }
291            String newValue = value;
292            if (key.startsWith(CmsFormatterConfig.FORMATTER_SETTINGS_KEY)) {
293                if (CmsUUID.isValidUUID(value)) {
294                    String newId = config.getMappedFormatterConfiguration(value);
295                    if (newId != null) {
296                        newValue = newId;
297                    }
298                } else if (value.startsWith(CmsFormatterConfig.SCHEMA_FORMATTER_ID)) {
299                    String schemaFormatterIdStr = value.substring(CmsFormatterConfig.SCHEMA_FORMATTER_ID.length());
300                    if (CmsUUID.isValidUUID(schemaFormatterIdStr)) {
301                        CmsUUID schemaFormatterId = new CmsUUID(schemaFormatterIdStr);
302                        CmsUUID mappedFormatterId = config.getMappedFormatterJspId(schemaFormatterId);
303                        if (mappedFormatterId != null) {
304                            newValue = CmsFormatterConfig.SCHEMA_FORMATTER_ID + mappedFormatterId;
305                        }
306                    }
307                }
308            }
309            newSettings.put(key, newValue);
310        }
311        CmsContainerElementBean newElement = element.clone();
312        newElement.updateIndividualSettings(newSettings);
313        CmsUUID formatterId = element.getFormatterId();
314        if ((formatterId == null) && m_forSave) {
315            try {
316                if (element.isGroupContainer(cms)) {
317                    // ID for group-container.jsp
318                    formatterId = new CmsUUID("e7029fa2-761e-11e0-bd7f-9ffeadaf4d46");
319                    newElement.setFormatterId(formatterId);
320                } else {
321                    if (OpenCms.getResourceManager().matchResourceType("function", element.getResource().getTypeId())) {
322                        formatterId = new CmsUUID("087ba7c9-e7fc-4336-acb8-d3416a4eb1fd");
323                        newElement.setFormatterId(formatterId);
324                    }
325                }
326            } catch (CmsException e) {
327                LOG.warn(e.getLocalizedMessage(), e);
328            }
329        }
330        CmsUUID mappedFormatterJspId = config.getMappedFormatterJspId(formatterId);
331        if (mappedFormatterJspId != null) {
332            newElement.setFormatterId(mappedFormatterJspId);
333        }
334        return newElement;
335    }
336
337    /**
338     * Loads the configuration file, using  CmsVfsMemoryObjectCache for caching.
339     *
340     * @param cms the CMS context
341     * @return the template mapper configuration
342     */
343    private CmsTemplateMapperConfiguration getConfiguration(final CmsObject cms) {
344
345        if (!m_enabled) {
346            return CmsTemplateMapperConfiguration.EMPTY_CONFIG;
347        }
348
349        if (m_configPath == null) {
350            m_configPath = OpenCms.getSystemInfo().getConfigFilePath(cms, "template-mapping.xml");
351        }
352
353        return (CmsTemplateMapperConfiguration)(CmsVfsMemoryObjectCache.getVfsMemoryObjectCache().loadVfsObject(
354            cms,
355            m_configPath,
356            new Transformer() {
357
358                @Override
359                public Object transform(Object input) {
360
361                    try {
362                        CmsFile file = cms.readFile(m_configPath, CmsResourceFilter.IGNORE_EXPIRATION);
363                        SAXReader saxBuilder = new SAXReader();
364                        try (ByteArrayInputStream stream = new ByteArrayInputStream(file.getContents())) {
365                            Document document = saxBuilder.read(stream);
366                            CmsTemplateMapperConfiguration config = new CmsTemplateMapperConfiguration(cms, document);
367                            return config;
368                        }
369                    } catch (Exception e) {
370                        LOG.warn(e.getLocalizedMessage(), e);
371                        return new CmsTemplateMapperConfiguration(); // empty configuration, does not do anything
372                    }
373
374                }
375            }));
376    }
377
378}