001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (C) Alkacon Software (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.containerpage;
029
030import org.opencms.cache.CmsVfsMemoryObjectCache;
031import org.opencms.file.CmsFile;
032import org.opencms.file.CmsObject;
033import org.opencms.file.CmsResource;
034import org.opencms.file.types.CmsResourceTypeFunctionConfig;
035import org.opencms.i18n.CmsLocaleManager;
036import org.opencms.main.CmsException;
037import org.opencms.util.CmsPair;
038import org.opencms.util.CmsStringUtil;
039import org.opencms.util.CmsUUID;
040import org.opencms.xml.containerpage.CmsDynamicFunctionBean.Format;
041import org.opencms.xml.content.CmsXmlContent;
042import org.opencms.xml.content.CmsXmlContentFactory;
043import org.opencms.xml.content.CmsXmlContentProperty;
044import org.opencms.xml.content.CmsXmlContentRootLocation;
045import org.opencms.xml.content.I_CmsXmlContentLocation;
046import org.opencms.xml.content.I_CmsXmlContentValueLocation;
047
048import java.util.ArrayList;
049import java.util.LinkedHashMap;
050import java.util.List;
051import java.util.Locale;
052import java.util.Map;
053
054/**
055 * The parser class for creating dynamic function beans from XML contents.<p>
056 */
057public class CmsDynamicFunctionParser {
058
059    /** The node name for the formatter settings. */
060    public static final String N_CONTAINER_SETTINGS = "ContainerSettings";
061
062    /**
063     * Parses a dynamic function bean given a resource.<p>
064     *
065     * @param cms the current CMS context
066     * @param res the resource from which to read the dynamic function
067     *
068     * @return the dynamic function bean created from the resource
069     *
070     * @throws CmsException if something goes wrong
071     */
072    public CmsDynamicFunctionBean parseFunctionBean(CmsObject cms, CmsResource res) throws CmsException {
073
074        CmsFile file = cms.readFile(res);
075        CmsXmlContent xmlContent = CmsXmlContentFactory.unmarshal(cms, file);
076        return parseFunctionBean(cms, xmlContent);
077    }
078
079    /**
080     * Parses a dynamic function bean from an in-memory XML content object.<p>
081     *
082     * @param cms the current CMS context
083     * @param content the XML content from which to read the dynamic function bean
084     *
085     * @return the dynamic function bean read from the XML content
086     *
087     * @throws CmsException if something goes wrong
088     */
089    public CmsDynamicFunctionBean parseFunctionBean(CmsObject cms, CmsXmlContent content) throws CmsException {
090
091        Locale locale = getLocaleToUse(cms, content);
092        String oldSiteRoot = cms.getRequestContext().getSiteRoot();
093        try {
094            cms.getRequestContext().setSiteRoot("");
095            CmsResource functionFormatter = getFunctionFormatter(cms);
096            CmsXmlContentRootLocation root = new CmsXmlContentRootLocation(content, locale);
097            CmsDynamicFunctionBean functionBean = parseFunctionBean(cms, root, content.getFile(), functionFormatter);
098            return functionBean;
099        } finally {
100            cms.getRequestContext().setSiteRoot(oldSiteRoot);
101        }
102    }
103
104    /**
105     * Parses all the additional formats from the XML content.<p>
106     *
107     * @param cms the current CMS context
108     * @param location the location from which to parse the additional formats
109     * @param functionRes the dynamic function resource
110     *
111     * @return the list of parsed formats
112     */
113    protected List<Format> getAdditionalFormats(
114        CmsObject cms,
115        I_CmsXmlContentLocation location,
116        CmsResource functionRes) {
117
118        List<I_CmsXmlContentValueLocation> locations = location.getSubValues("AdditionalFormat");
119        List<Format> formats = new ArrayList<Format>();
120        for (I_CmsXmlContentValueLocation formatLocation : locations) {
121            Format format = parseAdditionalFormat(cms, formatLocation, functionRes);
122            formats.add(format);
123        }
124        return formats;
125
126    }
127
128    /**
129     * Gets the function formatter resource, possibly from the cache.<p>
130     *
131     * @param cms the current CMS context
132     * @return the function formatter resource
133     *
134     * @throws CmsException if something goes wrong
135     */
136    protected CmsResource getFunctionFormatter(CmsObject cms) throws CmsException {
137
138        CmsVfsMemoryObjectCache cache = CmsVfsMemoryObjectCache.getVfsMemoryObjectCache();
139        String path = CmsResourceTypeFunctionConfig.FORMATTER_PATH;
140        Object cacheValue = cache.getCachedObject(cms, path);
141        if (cacheValue == null) {
142            CmsResource functionRes = cms.readResource(path);
143            cache.putCachedObject(cms, path, functionRes);
144            return functionRes;
145        } else {
146            return (CmsResource)cacheValue;
147        }
148    }
149
150    /**
151     * Gets the locale to use for parsing the dynamic function.<p>
152     *
153     * @param cms the current CMS context
154     * @param xmlContent the xml content from which the dynamic function should be read
155     *
156     * @return the locale from which the dynamic function should be read
157     */
158    protected Locale getLocaleToUse(CmsObject cms, CmsXmlContent xmlContent) {
159
160        Locale contextLocale = cms.getRequestContext().getLocale();
161        if (xmlContent.hasLocale(contextLocale)) {
162            return contextLocale;
163        }
164        Locale defaultLocale = CmsLocaleManager.getDefaultLocale();
165        if (xmlContent.hasLocale(defaultLocale)) {
166            return defaultLocale;
167        }
168        if (!xmlContent.getLocales().isEmpty()) {
169            return xmlContent.getLocales().get(0);
170        } else {
171            return defaultLocale;
172        }
173    }
174
175    /**
176     * Parses the main format from the XML content.<p>
177     * @param cms the current CMS context
178     * @param location the location from which to parse main format
179     * @param functionRes the dynamic function resource
180     *
181     * @return the parsed main format
182     */
183    protected Format getMainFormat(CmsObject cms, I_CmsXmlContentLocation location, CmsResource functionRes) {
184
185        I_CmsXmlContentValueLocation jspLoc = location.getSubValue("FunctionProvider");
186        CmsUUID structureId = jspLoc.asId(cms);
187        I_CmsXmlContentValueLocation containerSettings = location.getSubValue("ContainerSettings");
188        Map<String, String> parameters = parseParameters(cms, location, "Parameter");
189        if (containerSettings != null) {
190            String type = getStringValue(cms, containerSettings.getSubValue("Type"), "");
191            String minWidth = getStringValue(cms, containerSettings.getSubValue("MinWidth"), "");
192            String maxWidth = getStringValue(cms, containerSettings.getSubValue("MaxWidth"), "");
193            Format result = new Format(structureId, type, minWidth, maxWidth, parameters);
194            return result;
195        } else {
196            Format result = new Format(structureId, "", "", "", parameters);
197            result.setNoContainerSettings(true);
198            return result;
199        }
200    }
201
202    /**
203     * Gets the string value of an XML content location.<p>
204     *
205     * @param cms the current CMS context
206     * @param location an XML content location
207     *
208     * @return the string value of that XML content location
209     */
210    protected String getString(CmsObject cms, I_CmsXmlContentValueLocation location) {
211
212        if (location == null) {
213            return null;
214        }
215        return location.asString(cms);
216    }
217
218    /**
219     * Converts a (possibly null) content value location to a string.<p>
220     *
221     * @param cms the current CMS context
222     * @param location the content value location
223     * @param defaultValue the value to return if the location is null
224     *
225     * @return the string value of the content value location
226     */
227    protected String getStringValue(CmsObject cms, I_CmsXmlContentValueLocation location, String defaultValue) {
228
229        if (location == null) {
230            return defaultValue;
231        }
232        return location.asString(cms);
233    }
234
235    /**
236     * Parses an additional format from the XML content.<p>
237     *
238     * @param cms the current CMS context
239     *
240     * @param location the location from which to parse the additional format
241     * @param functionRes the dynamic function resource
242     *
243     * @return the additional format
244     */
245    protected Format parseAdditionalFormat(
246        CmsObject cms,
247        I_CmsXmlContentValueLocation location,
248        CmsResource functionRes) {
249
250        I_CmsXmlContentValueLocation jspLoc = location.getSubValue("FunctionProvider");
251        CmsUUID structureId = jspLoc.asId(cms);
252        I_CmsXmlContentValueLocation minWidthLoc = location.getSubValue("MinWidth");
253        String minWidth = getStringValue(cms, minWidthLoc, "");
254        I_CmsXmlContentValueLocation maxWidthLoc = location.getSubValue("MaxWidth");
255        String maxWidth = getStringValue(cms, maxWidthLoc, "");
256        I_CmsXmlContentValueLocation typeLoc = location.getSubValue("Type");
257        String type = getStringValue(cms, typeLoc, "");
258        Map<String, String> parameters = parseParameters(cms, location, "Parameter");
259        return new Format(structureId, type, minWidth, maxWidth, parameters);
260    }
261
262    /**
263     * Parses a dynamic function bean.<p>
264     *
265     * @param cms the current CMS context
266     * @param location the location from which to parse the dynamic function bean
267     * @param functionRes the dynamic function resource
268     * @param functionFormatter the function formatter resource
269     *
270     * @return the parsed dynamic function bean
271     */
272    protected CmsDynamicFunctionBean parseFunctionBean(
273        CmsObject cms,
274        I_CmsXmlContentLocation location,
275        CmsResource functionRes,
276        CmsResource functionFormatter) {
277
278        Format mainFormat = getMainFormat(cms, location, functionRes);
279        List<Format> otherFormats = getAdditionalFormats(cms, location, functionRes);
280        Map<String, CmsXmlContentProperty> definedSettings = parseSettings(cms, location, functionRes);
281        CmsDynamicFunctionBean result = new CmsDynamicFunctionBean(
282            mainFormat,
283            otherFormats,
284            definedSettings,
285            functionRes,
286            functionFormatter);
287        return result;
288    }
289
290    /**
291     * Parses a request parameter for the JSP from the XML content.<p>
292     *
293     * @param cms the current CMS context
294     * @param valueLocation the location from which to parse the parameter
295     *
296     * @return the parsed parameter key/value pair
297     */
298    protected CmsPair<String, String> parseParameter(CmsObject cms, I_CmsXmlContentValueLocation valueLocation) {
299
300        String key = valueLocation.getSubValue("Key").asString(cms);
301        String value = valueLocation.getSubValue("Value").asString(cms);
302        return CmsPair.create(key, value);
303    }
304
305    /**
306     * Parses all parameters for the JSP from the XML content.<p>
307     *
308     * @param cms the current CMS context
309     * @param location the location from which to read the parameters
310     * @param name the name of the tag from which to read the parameters
311     *
312     * @return the parsed map of parameters
313     */
314    protected Map<String, String> parseParameters(CmsObject cms, I_CmsXmlContentLocation location, String name) {
315
316        List<I_CmsXmlContentValueLocation> locations = location.getSubValues(name);
317        Map<String, String> result = new LinkedHashMap<String, String>();
318        for (I_CmsXmlContentValueLocation paramLocation : locations) {
319            CmsPair<String, String> param = parseParameter(cms, paramLocation);
320            result.put(param.getFirst(), param.getSecond());
321        }
322        return result;
323    }
324
325    /**
326     * Helper method for parsing a settings definition.<p>
327     *
328     * @param cms the current CMS context
329     * @param field the node from which to read the settings definition
330     *
331     * @return the parsed setting definition
332     */
333    protected CmsXmlContentProperty parseProperty(CmsObject cms, I_CmsXmlContentLocation field) {
334
335        String name = getString(cms, field.getSubValue("PropertyName"));
336        String widget = getString(cms, field.getSubValue("Widget"));
337        if (CmsStringUtil.isEmptyOrWhitespaceOnly(widget)) {
338            widget = "string";
339        }
340        String type = getString(cms, field.getSubValue("Type"));
341        if (CmsStringUtil.isEmptyOrWhitespaceOnly(type)) {
342            type = "string";
343        }
344        String widgetConfig = getString(cms, field.getSubValue("WidgetConfig"));
345        String ruleRegex = getString(cms, field.getSubValue("RuleRegex"));
346        String ruleType = getString(cms, field.getSubValue("RuleType"));
347        String default1 = getString(cms, field.getSubValue("Default"));
348        String error = getString(cms, field.getSubValue("Error"));
349        String niceName = getString(cms, field.getSubValue("DisplayName"));
350        String description = getString(cms, field.getSubValue("Description"));
351
352        CmsXmlContentProperty prop = new CmsXmlContentProperty(
353            name,
354            type,
355            widget,
356            widgetConfig,
357            ruleRegex,
358            ruleType,
359            default1,
360            niceName,
361            description,
362            error,
363            "true");
364        return prop;
365    }
366
367    /**
368     * Parses the settings for the dynamic function from the XML content.<p>
369     *
370     * @param cms the current CMS context
371     * @param location the location from which to read the dynamic function settings
372     * @param functionResource the dynamic function resource
373     *
374     * @return the parsed map of settings for the dynamic function
375     */
376    protected Map<String, CmsXmlContentProperty> parseSettings(
377        CmsObject cms,
378        I_CmsXmlContentLocation location,
379        CmsResource functionResource) {
380
381        LinkedHashMap<String, CmsXmlContentProperty> settingConfigs = new LinkedHashMap<String, CmsXmlContentProperty>();
382        List<I_CmsXmlContentValueLocation> locations = location.getSubValues("SettingConfig");
383        for (I_CmsXmlContentValueLocation settingLoc : locations) {
384            CmsXmlContentProperty settingConfigBean = parseProperty(cms, settingLoc);
385            settingConfigs.put(settingConfigBean.getName(), settingConfigBean);
386        }
387        return settingConfigs;
388    }
389
390}