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 = new CmsContainerBean(
185                container.getName(),
186                container.getType(),
187                container.getParentInstanceId(),
188                container.isRootContainer(),
189                newElements);
190            newContainers.add(newContainer);
191        }
192        CmsContainerPageBean result = new CmsContainerPageBean(newContainers);
193        return result;
194    }
195
196    /**
197     * Transforms a container element bean used for detail elements.<p>
198     *
199     * @param cms the current CMS context
200     * @param input the bean to be transformed
201     * @param rootPath the root path of the page
202     *
203     * @return the transformed bean
204     */
205    public CmsContainerElementBean transformDetailElement(
206        CmsObject cms,
207        CmsContainerElementBean input,
208        String rootPath) {
209
210        CmsTemplateMapperConfiguration config = getConfiguration(cms);
211        if ((config == null) || !config.isEnabledForPath(rootPath)) {
212            return input;
213        }
214        return transformContainerElement(cms, config, input);
215
216    }
217
218    /**
219     * Transforms a group container bean.<p>
220     *
221     * @param cms the current CMS context
222     * @param input the input bean to be transformed
223     * @param rootPath the root path of the container page
224     *
225     * @return the transformed bean
226     */
227    public CmsGroupContainerBean transformGroupContainer(CmsObject cms, CmsGroupContainerBean input, String rootPath) {
228
229        CmsTemplateMapperConfiguration config = getConfiguration(cms);
230        if ((config == null) || !config.isEnabledForPath(rootPath)) {
231            return input;
232        }
233        List<CmsContainerElementBean> newElements = new ArrayList<>();
234        for (CmsContainerElementBean element : input.getElements()) {
235            CmsContainerElementBean newElement = transformContainerElement(cms, config, element);
236            if (newElement != null) {
237                newElements.add(newElement);
238            }
239        }
240        Set<String> transformedTypes = new HashSet<>();
241        Set<String> oldTypes = input.getTypes();
242        if (oldTypes == null) {
243            oldTypes = new HashSet<>();
244        }
245        for (String type : oldTypes) {
246            String newType = config.getMappedElementGroupType(type);
247            if (newType == null) {
248                newType = type;
249            }
250            transformedTypes.add(newType);
251        }
252
253        CmsGroupContainerBean result = new CmsGroupContainerBean(
254            input.getTitle(),
255            input.getDescription(),
256            newElements,
257            transformedTypes);
258        return result;
259    }
260
261    /**
262     * Helper method to transform a single container element.<p>
263     * @param cms the CMS context
264     * @param config the configuration
265     * @param element the container element to be transformed
266     *
267     * @return the transformed bean
268     */
269    protected CmsContainerElementBean transformContainerElement(
270        CmsObject cms,
271        CmsTemplateMapperConfiguration config,
272        CmsContainerElementBean element) {
273
274        if (m_forSave) {
275            try {
276                element.initResource(cms);
277            } catch (Exception e) {
278                LOG.error(e.getLocalizedMessage(), e);
279                return null;
280            }
281        }
282        Map<String, String> settings = element.getIndividualSettings();
283        if (settings == null) {
284            settings = new HashMap<>();
285        }
286        Map<String, String> newSettings = new HashMap<>();
287        for (Map.Entry<String, String> entry : settings.entrySet()) {
288            String key = entry.getKey();
289            if (CmsTemplateContextInfo.SETTING.equals(key)) {
290                continue;
291            }
292            String value = entry.getValue();
293            if (value == null) {
294                continue;
295            }
296            String newValue = value;
297            if (key.startsWith(CmsFormatterConfig.FORMATTER_SETTINGS_KEY)) {
298                if (CmsUUID.isValidUUID(value)) {
299                    String newId = config.getMappedFormatterConfiguration(value);
300                    if (newId != null) {
301                        newValue = newId;
302                    }
303                } else if (value.startsWith(CmsFormatterConfig.SCHEMA_FORMATTER_ID)) {
304                    String schemaFormatterIdStr = value.substring(CmsFormatterConfig.SCHEMA_FORMATTER_ID.length());
305                    if (CmsUUID.isValidUUID(schemaFormatterIdStr)) {
306                        CmsUUID schemaFormatterId = new CmsUUID(schemaFormatterIdStr);
307                        CmsUUID mappedFormatterId = config.getMappedFormatterJspId(schemaFormatterId);
308                        if (mappedFormatterId != null) {
309                            newValue = CmsFormatterConfig.SCHEMA_FORMATTER_ID + mappedFormatterId;
310                        }
311                    }
312                }
313            }
314            newSettings.put(key, newValue);
315        }
316        CmsContainerElementBean newElement = element.clone();
317        newElement.updateIndividualSettings(newSettings);
318        CmsUUID formatterId = element.getFormatterId();
319        if ((formatterId == null) && m_forSave) {
320            try {
321                if (element.isGroupContainer(cms)) {
322                    // ID for group-container.jsp
323                    formatterId = new CmsUUID("e7029fa2-761e-11e0-bd7f-9ffeadaf4d46");
324                    newElement.setFormatterId(formatterId);
325                } else if (OpenCms.getResourceManager().matchResourceType(
326                    "function",
327                    element.getResource().getTypeId())) {
328                    formatterId = new CmsUUID("087ba7c9-e7fc-4336-acb8-d3416a4eb1fd");
329                    newElement.setFormatterId(formatterId);
330                }
331            } catch (CmsException e) {
332                LOG.warn(e.getLocalizedMessage(), e);
333            }
334        }
335        CmsUUID mappedFormatterJspId = config.getMappedFormatterJspId(formatterId);
336        if (mappedFormatterJspId != null) {
337            newElement.setFormatterId(mappedFormatterJspId);
338        }
339        return newElement;
340    }
341
342    /**
343     * Loads the configuration file, using  CmsVfsMemoryObjectCache for caching.
344     *
345     * @param cms the CMS context
346     * @return the template mapper configuration
347     */
348    private CmsTemplateMapperConfiguration getConfiguration(final CmsObject cms) {
349
350        if (!m_enabled) {
351            return CmsTemplateMapperConfiguration.EMPTY_CONFIG;
352        }
353
354        if (m_configPath == null) {
355            m_configPath = OpenCms.getSystemInfo().getConfigFilePath(cms, "template-mapping.xml");
356        }
357
358        return (CmsTemplateMapperConfiguration)(CmsVfsMemoryObjectCache.getVfsMemoryObjectCache().loadVfsObject(
359            cms,
360            m_configPath,
361            new Transformer() {
362
363                @Override
364                public Object transform(Object input) {
365
366                    try {
367                        CmsFile file = cms.readFile(m_configPath, CmsResourceFilter.IGNORE_EXPIRATION);
368                        SAXReader saxBuilder = new SAXReader();
369                        try (ByteArrayInputStream stream = new ByteArrayInputStream(file.getContents())) {
370                            Document document = saxBuilder.read(stream);
371                            CmsTemplateMapperConfiguration config = new CmsTemplateMapperConfiguration(cms, document);
372                            return config;
373                        }
374                    } catch (Exception e) {
375                        LOG.warn(e.getLocalizedMessage(), e);
376                        return new CmsTemplateMapperConfiguration(); // empty configuration, does not do anything
377                    }
378
379                }
380            }));
381    }
382
383}