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.jsp.search.config.parser.simplesearch;
029
030import org.opencms.file.CmsFile;
031import org.opencms.file.CmsObject;
032import org.opencms.file.CmsResource;
033import org.opencms.i18n.CmsLocaleManager;
034import org.opencms.jsp.search.config.parser.simplesearch.CmsConfigurationBean.CombinationMode;
035import org.opencms.jsp.search.config.parser.simplesearch.daterestrictions.CmsDateRestrictionParser;
036import org.opencms.jsp.search.config.parser.simplesearch.daterestrictions.I_CmsDateRestriction;
037import org.opencms.jsp.search.config.parser.simplesearch.preconfiguredrestrictions.CmsRestrictionsBean;
038import org.opencms.main.CmsException;
039import org.opencms.main.CmsLog;
040import org.opencms.relations.CmsCategoryService;
041import org.opencms.relations.CmsLink;
042import org.opencms.util.CmsGeoUtil;
043import org.opencms.util.CmsStringUtil;
044import org.opencms.util.CmsUUID;
045import org.opencms.xml.CmsXmlUtils;
046import org.opencms.xml.content.CmsXmlContent;
047import org.opencms.xml.content.CmsXmlContentFactory;
048import org.opencms.xml.content.CmsXmlContentValueLocation;
049import org.opencms.xml.types.CmsXmlVfsFileValue;
050import org.opencms.xml.types.I_CmsXmlContentValue;
051
052import java.util.ArrayList;
053import java.util.Arrays;
054import java.util.Collections;
055import java.util.HashMap;
056import java.util.LinkedHashMap;
057import java.util.List;
058import java.util.Locale;
059import java.util.Map;
060import java.util.stream.Collectors;
061
062import org.apache.commons.logging.Log;
063
064/** Utils to read and update the list configuration. */
065public final class CmsConfigParserUtils {
066
067    /** The logger for this class. */
068    static final Log LOG = CmsLog.getLog(CmsConfigParserUtils.class.getName());
069
070    /** List configuration node name and field key. */
071    public static final String N_BLACKLIST = "Blacklist";
072
073    /** List configuration node name and field key. */
074    public static final String N_CATEGORY = "Category";
075
076    /** List configuration node name and field key. */
077    private static final String N_CATEGORY_FOLDER_RESTRICTION = "CategoryFolderFilter";
078
079    /** List configuration node name and field key. */
080    private static final String N_COORDINATES = "Coordinates";
081
082    /** List configuration node name and field key. */
083    private static final String N_FOLDER = "Folder";
084
085    /** List configuration node name for the category mode. */
086    public static final String N_CATEGORY_MODE = "CategoryMode";
087
088    /** XML content node name. */
089    public static final String N_DATE_RESTRICTION = "DateRestriction";
090
091    /** List configuration node name and field key. */
092    public static final String N_DISPLAY_TYPE = "TypesToCollect";
093
094    /** List configuration node name and field key. */
095    public static final String N_FILTER_MULTI_DAY = "FilterMultiDay";
096
097    /** List configuration node name and field key. */
098    public static final String N_FILTER_QUERY = "FilterQuery";
099
100    /** List configuration node name and field key. */
101    public static final String N_GEO_FILTER = "GeoFilter";
102
103    /** List configuration node name and field key. */
104    public static final String N_KEY = "Key";
105
106    /** List configuration node name and field key. */
107    public static final String N_PARAMETER = "Parameter";
108
109    /** List configuration node name and field key. */
110    public static final String N_RADIUS = "Radius";
111
112    /** List configuration node name and field key. */
113    public static final String N_SEARCH_FOLDER = "SearchFolder";
114
115    /** List configuration node name and field key. */
116    public static final String N_SHOW_EXPIRED = "ShowExpired";
117
118    /** List configuration node name and field key. */
119    public static final String N_SORT_ORDER = "SortOrder";
120
121    /** List configuration node name and field key. */
122    public static final String N_TITLE = "Title";
123
124    /** List configuration node name and field key. */
125    public static final String N_VALUE = "Value";
126
127    /** List configuration node name and field key. */
128    public static final String N_MAX_RESULTS = "MaxResults";
129
130    /** List configuration node name and field key. */
131    public static final String N_PRECONFIGURED_FILTER_QUERY = "PreconfiguredFilterQuery";
132
133    /** List configuration node name and field key. */
134    public static final String N_RULE = "Rule";
135
136    /** The parameter fields. */
137    public static final String[] PARAMETER_FIELDS = new String[] {
138        N_TITLE,
139        N_CATEGORY,
140        N_FILTER_MULTI_DAY,
141        N_FILTER_QUERY,
142        N_SORT_ORDER,
143        N_SHOW_EXPIRED,
144        N_MAX_RESULTS};
145
146    /** Statically initialized map from node names to parameter names of the list configuration bean. */
147    private static Map<String, String> PARAMS_MAP = createParamsMap();
148
149    /**
150     * Parses the list configuration resource.<p>
151     *
152     * @param cms the CMS context to use
153     * @param res the list configuration resource
154     *
155     * @return the configuration data bean
156     */
157    public static CmsConfigurationBean parseListConfiguration(CmsObject cms, CmsResource res) {
158
159        CmsConfigurationBean result = new CmsConfigurationBean();
160        try {
161            CmsFile configFile = cms.readFile(res);
162            CmsXmlContent content = CmsXmlContentFactory.unmarshal(cms, configFile);
163            Locale locale = CmsLocaleManager.MASTER_LOCALE;
164
165            if (!content.hasLocale(locale)) {
166                locale = content.getLocales().get(0);
167            }
168            for (String field : PARAMETER_FIELDS) {
169                String val = content.getStringValue(cms, field, locale);
170                if (N_CATEGORY.equals(field)) {
171                    if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(val)) {
172                        result.setCategories(Arrays.asList(val.split(",")));
173                    } else {
174                        result.setCategories(Collections.<String> emptyList());
175                    }
176                } else {
177                    String param = PARAMS_MAP.get(field);
178                    if (param != null) {
179                        result.setParameterValue(param, val);
180                    } else {
181                        LOG.error("Parameter value for field \"" + field + "\" is unknown and hence ignored.");
182                    }
183                }
184            }
185
186            I_CmsXmlContentValue restrictValue = content.getValue(N_DATE_RESTRICTION, locale);
187            if (restrictValue != null) {
188                CmsDateRestrictionParser parser = new CmsDateRestrictionParser(cms);
189                I_CmsDateRestriction restriction = parser.parse(new CmsXmlContentValueLocation(restrictValue));
190                if (restriction == null) {
191                    LOG.warn(
192                        "Improper date restriction configuration in content "
193                            + content.getFile().getRootPath()
194                            + ", online="
195                            + cms.getRequestContext().getCurrentProject().isOnlineProject());
196                }
197                result.setDateRestriction(restriction);
198            }
199
200            I_CmsXmlContentValue geoFilterValue = content.getValue(N_GEO_FILTER, locale);
201            if (geoFilterValue != null) {
202                String coordinatesPath = geoFilterValue.getPath() + "/" + N_COORDINATES;
203                String radiusPath = geoFilterValue.getPath() + "/" + N_RADIUS;
204                I_CmsXmlContentValue coordinatesValue = content.getValue(coordinatesPath, locale);
205                I_CmsXmlContentValue radiusValue = content.getValue(radiusPath, locale);
206                String coordinates = CmsGeoUtil.parseCoordinates(coordinatesValue.getStringValue(cms));
207                String radius = radiusValue.getStringValue(cms);
208                boolean radiusValid = false;
209                try {
210                    Float.parseFloat(radius);
211                    radiusValid = true;
212                } catch (NumberFormatException e) {
213                    radiusValid = false;
214                }
215                if ((coordinates != null) && radiusValid) {
216                    CmsGeoFilterBean listGeoFilterBean = new CmsGeoFilterBean(coordinates, radius);
217                    result.setGeoFilter(listGeoFilterBean);
218                } else {
219                    LOG.warn(
220                        "Improper Geo filter in content "
221                            + content.getFile().getRootPath()
222                            + ", online="
223                            + cms.getRequestContext().getCurrentProject().isOnlineProject());
224                }
225            }
226
227            I_CmsXmlContentValue categoryModeVal = content.getValue(N_CATEGORY_MODE, locale);
228            CombinationMode categoryMode = CombinationMode.OR;
229            if (categoryModeVal != null) {
230                try {
231                    categoryMode = CombinationMode.valueOf(categoryModeVal.getStringValue(cms));
232                } catch (Exception e) {
233                    LOG.error(e.getLocalizedMessage(), e);
234                }
235            }
236            result.setCategoryMode(categoryMode);
237
238            LinkedHashMap<String, String> parameters = new LinkedHashMap<String, String>();
239            for (I_CmsXmlContentValue parameter : content.getValues(N_PARAMETER, locale)) {
240                I_CmsXmlContentValue keyVal = content.getValue(parameter.getPath() + "/" + N_KEY, locale);
241                I_CmsXmlContentValue valueVal = content.getValue(parameter.getPath() + "/" + N_VALUE, locale);
242                if ((keyVal != null)
243                    && CmsStringUtil.isNotEmptyOrWhitespaceOnly(keyVal.getStringValue(cms))
244                    && (valueVal != null)) {
245                    parameters.put(keyVal.getStringValue(cms), valueVal.getStringValue(cms));
246                }
247            }
248            result.setAdditionalParameters(parameters);
249            List<String> displayTypes = new ArrayList<String>();
250            List<I_CmsXmlContentValue> typeValues = content.getValues(N_DISPLAY_TYPE, locale);
251            if (!typeValues.isEmpty()) {
252                for (I_CmsXmlContentValue value : typeValues) {
253                    displayTypes.add(value.getStringValue(cms));
254                }
255            }
256            result.setDisplayTypes(displayTypes);
257            List<String> folders = new ArrayList<String>();
258            List<I_CmsXmlContentValue> folderValues = content.getValues(N_SEARCH_FOLDER, locale);
259            if (!folderValues.isEmpty()) {
260                for (I_CmsXmlContentValue value : folderValues) {
261                    CmsLink val = ((CmsXmlVfsFileValue)value).getLink(cms);
262                    if (val != null) {
263                        // we are using root paths
264                        folders.add(cms.getRequestContext().addSiteRoot(val.getSitePath(cms)));
265                    }
266                }
267            }
268            result.setFolders(folders);
269            List<CmsUUID> blackList = new ArrayList<CmsUUID>();
270            List<I_CmsXmlContentValue> blacklistValues = content.getValues(N_BLACKLIST, locale);
271            if (!blacklistValues.isEmpty()) {
272                for (I_CmsXmlContentValue value : blacklistValues) {
273                    CmsLink link = ((CmsXmlVfsFileValue)value).getLink(cms);
274                    if (link != null) {
275                        blackList.add(link.getStructureId());
276                    }
277                }
278            }
279            result.setBlacklist(blackList);
280            List<I_CmsXmlContentValue> categoryFolderRestrictions = content.getValues(
281                N_CATEGORY_FOLDER_RESTRICTION,
282                locale);
283            if (!categoryFolderRestrictions.isEmpty()) {
284                for (I_CmsXmlContentValue restriction : categoryFolderRestrictions) {
285                    List<String> restrictionFolders = new ArrayList<>();
286                    List<I_CmsXmlContentValue> folderVals = content.getValues(
287                        CmsXmlUtils.concatXpath(restriction.getPath(), N_FOLDER),
288                        locale);
289                    for (I_CmsXmlContentValue folderVal : folderVals) {
290                        CmsLink val = ((CmsXmlVfsFileValue)folderVal).getLink(cms);
291                        if (val != null) {
292                            // we are using root paths
293                            restrictionFolders.add(cms.getRequestContext().addSiteRoot(val.getSitePath(cms)));
294                        }
295                    }
296                    List<String> restrictionCategorySitePaths;
297                    I_CmsXmlContentValue categoryVal = content.getValue(
298                        CmsXmlUtils.concatXpath(restriction.getPath(), N_CATEGORY),
299                        locale);
300                    String categoryString = null != categoryVal ? categoryVal.getStringValue(cms) : "";
301                    if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(categoryString)) {
302                        restrictionCategorySitePaths = Arrays.asList(categoryString.split(","));
303                    } else {
304                        restrictionCategorySitePaths = Collections.<String> emptyList();
305                    }
306                    List<String> restrictionCategories = new ArrayList<>(restrictionCategorySitePaths.size());
307                    for (String sitePath : restrictionCategorySitePaths) {
308                        try {
309                            String path = CmsCategoryService.getInstance().getCategory(
310                                cms,
311                                cms.getRequestContext().addSiteRoot(sitePath)).getPath();
312                            restrictionCategories.add(path);
313                        } catch (CmsException e) {
314                            LOG.warn(e.getLocalizedMessage(), e);
315                        }
316                    }
317                    String restrictionCategoryMode = content.getValue(
318                        CmsXmlUtils.concatXpath(restriction.getPath(), N_CATEGORY_MODE),
319                        locale).getStringValue(cms);
320                    result.addCategoryFolderFilter(
321                        new CmsCategoryFolderRestrictionBean(
322                            restrictionCategories,
323                            restrictionFolders,
324                            null == restrictionCategoryMode ? null : CombinationMode.valueOf(restrictionCategoryMode)));
325
326                }
327            }
328            List<I_CmsXmlContentValue> preconfiguredRestrictions = content.getValues(
329                N_PRECONFIGURED_FILTER_QUERY,
330                locale);
331            if (!preconfiguredRestrictions.isEmpty()) {
332                CmsRestrictionsBean restrictionBean = new CmsRestrictionsBean();
333                for (I_CmsXmlContentValue restriction : preconfiguredRestrictions) {
334                    String restrictionRule = content.getValue(
335                        CmsXmlUtils.concatXpath(restriction.getPath(), N_RULE),
336                        locale).getStringValue(cms);
337                    List<I_CmsXmlContentValue> restrictionVals = content.getValues(
338                        CmsXmlUtils.concatXpath(restriction.getPath(), N_VALUE),
339                        locale);
340                    List<String> restrictionValues = restrictionVals.stream().map(v -> v.getStringValue(cms)).collect(
341                        Collectors.toList());
342                    restrictionBean.addRestriction(restrictionRule, restrictionValues);
343                }
344                result.setPreconfiguredRestrictions(restrictionBean);
345            }
346        } catch (CmsException e) {
347            e.printStackTrace();
348        }
349        return result;
350    }
351
352    /**
353     * Updates the black list entries in the provided xml content.
354     * @param cms the cms context.
355     * @param content the xml content to update (must be of type list_config)
356     * @param configBean the config bean to get the blacklist entries from.
357     * @return the updated content (update is in-place).
358     */
359    public static CmsXmlContent updateBlackList(
360        CmsObject cms,
361        CmsXmlContent content,
362        CmsConfigurationBean configBean) {
363
364        // list configurations are single locale contents
365        Locale locale = CmsLocaleManager.MASTER_LOCALE;
366        int count = 0;
367        while (content.hasValue(N_BLACKLIST, locale)) {
368            content.removeValue(N_BLACKLIST, locale, 0);
369        }
370        for (CmsUUID hiddenId : configBean.getBlacklist()) {
371            CmsXmlVfsFileValue contentVal;
372            contentVal = (CmsXmlVfsFileValue)content.addValue(cms, N_BLACKLIST, locale, count);
373            contentVal.setIdValue(cms, hiddenId);
374            count++;
375        }
376        return content;
377    }
378
379    /**
380     * Creates the parameter map, mapping from noed names to parameters of the list configuration bean.
381     * @return the parameter map.
382     */
383    private static Map<String, String> createParamsMap() {
384
385        Map<String, String> result = new HashMap<>();
386        result.put(N_TITLE, CmsConfigurationBean.PARAM_TITLE);
387        result.put(N_FILTER_MULTI_DAY, CmsConfigurationBean.PARAM_FILTER_MULTI_DAY);
388        result.put(N_FILTER_QUERY, CmsConfigurationBean.PARAM_FILTER_QUERY);
389        result.put(N_SORT_ORDER, CmsConfigurationBean.PARAM_SORT_ORDER);
390        result.put(N_SHOW_EXPIRED, CmsConfigurationBean.PARAM_SHOW_EXPIRED);
391        result.put(N_MAX_RESULTS, CmsConfigurationBean.PARAM_MAX_RESULTS);
392        return Collections.unmodifiableMap(result);
393    }
394
395}