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 GmbH & Co. KG, 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.file.collectors;
029
030import org.opencms.file.CmsDataAccessException;
031import org.opencms.file.CmsObject;
032import org.opencms.file.CmsResource;
033import org.opencms.file.CmsResourceFilter;
034import org.opencms.file.I_CmsResource;
035import org.opencms.file.types.I_CmsResourceType;
036import org.opencms.loader.CmsLoaderException;
037import org.opencms.main.CmsException;
038import org.opencms.main.CmsLog;
039import org.opencms.main.OpenCms;
040import org.opencms.relations.CmsCategory;
041import org.opencms.relations.CmsCategoryService;
042import org.opencms.util.CmsStringUtil;
043
044import java.util.ArrayList;
045import java.util.Arrays;
046import java.util.Collections;
047import java.util.Comparator;
048import java.util.HashMap;
049import java.util.Iterator;
050import java.util.List;
051import java.util.Map;
052
053import org.apache.commons.logging.Log;
054
055/**
056 * A collector to fetch XML contents in a folder or the current site filtered by one or more given category types.<p>
057 *
058 * The return list will also be filtered by given key value pairs which are given as a collector parameter.<p>
059 *
060 * Usage:
061 * <code>
062 * &lt;cms:contentload collector=&quot;allKeyValuePairFiltered&quot; param=&quot;resource=[filename]|resourceType=[resource type]|categoryTypes=[category1,category2,...]|subTree=[boolean]|sortBy=[category|date|property:[property_name]]|sortAsc=[boolean]&quot;&gt;
063 * </code>
064 *
065 * @since 7.0.0
066 */
067public class CmsCategoryResourceCollector extends A_CmsResourceCollector {
068
069    /**
070     * Data structure for the collector, parsed from the collector parameters.<p>
071     *
072     * In addition to the superclass this implementation accepts parameters that build key value pairs separated by
073     * pipes '|', which allows arbitrary order of parameters and free numbers of parameters.<p>
074     *
075     * Usage:
076     * <code>
077     * &quot;resource=[filename]|resourceType=[resource type]|categoryTypes=[category1,category2,...]|excludeTimerange=false|subTree=[boolean]|sortBy=[category|date|property:[property_name]]|sortAsc=[boolean]&quot;
078     * </code>
079     */
080    private static final class CmsCategoryCollectorData extends CmsCollectorData {
081
082        /** The collector parameter key for the resource type. */
083        public static final String PARAM_KEY_CATEGORY_TYPES = "categoryTypes";
084
085        /** The collector parameter key for the count. */
086        public static final String PARAM_KEY_COUNT = "count";
087
088        /** The collector parameter key for the resource (folder / file). */
089        public static final String PARAM_KEY_RESOURCE = "resource";
090
091        /** The collector parameter key for the resource type. */
092        public static final String PARAM_KEY_RESOURCE_TYPE = "resourceType";
093
094        /** The collector parameter key for sort ascending. */
095        public static final String PARAM_KEY_SORT_ASC = "sortAsc";
096
097        /** The collector parameter key for sort by. */
098        public static final String PARAM_KEY_SORT_BY = "sortBy";
099
100        /** The collector parameter key for the sub tree. */
101        public static final String PARAM_KEY_SUB_TREE = "subTree";
102
103        /** The list of category types. */
104        private List<String> m_categoryTypes;
105
106        /** Indicates if the returned list will be sorted ascending or not (descending). */
107        private boolean m_sortAsc;
108
109        /** The returned list will be sort by this ('category', 'date' or 'property' are excepted). */
110        private String m_sortBy;
111
112        /** The returned list will be sort by this property value.  */
113        private String m_sortByPropertyName;
114
115        /** Indicates if the sub tree of the given resource will be searched for appropriate resources too. */
116        private boolean m_subTree;
117
118        /**
119         * Creates a new collector data set.<p>
120         *
121         * @param data the data to parse.
122         *
123         * @throws CmsLoaderException if the given configuration is not valid.
124         */
125        public CmsCategoryCollectorData(String data)
126        throws CmsLoaderException {
127
128            parseExtendedData(data);
129        }
130
131        /**
132         * Returns the list of requested categories.<p>
133         *
134         * @return the list of requested categories
135         */
136        public List<String> getCategoryTypes() {
137
138            return m_categoryTypes;
139        }
140
141        /**
142         * Returns the sort by string (only 'date', 'category' or 'property' excepted).<p>
143         *
144         * @return the sort by string
145         */
146        public String getSortBy() {
147
148            return m_sortBy;
149        }
150
151        /**
152         * Returns the sort by this property value.<p>
153         *
154         * @return the sort by string
155         */
156        public String getSortByPropertyName() {
157
158            return m_sortByPropertyName;
159        }
160
161        /**
162         * Returns the sort order. <code>true=asc</code> or <code>false=desc</code>  <p>
163         *
164         * @return the sort order. <code>true=asc</code> or <code>false=desc</code>
165         */
166        public boolean getSortOrder() {
167
168            return m_sortAsc;
169        }
170
171        /**
172         * Returns <code>true</code> if the list has to be sorted in ascending order.<p>
173         *
174         * @return <code>true</code> if the list has to be sorted in ascending order
175         */
176        public boolean isSortAsc() {
177
178            return m_sortAsc;
179        }
180
181        /**
182         * Returns <code>true</code> if the sub tree of the given resource will be searched too.<p>
183         *
184         * @return <code>true</code> if the sub tree of the given resource will be searched too.
185         */
186        public boolean isSubTree() {
187
188            return m_subTree;
189        }
190
191        /**
192         * Parses the additional configuration data from the collector param.<p>
193         *
194         * @param data the configuration data.
195         *
196         * @throws CmsLoaderException if something goes wrong
197         */
198        private void parseExtendedData(String data) throws CmsLoaderException {
199
200            String[] keyValueTokens = CmsStringUtil.splitAsArray(data, '|');
201            setType(-1);
202            for (int i = keyValueTokens.length - 1; i >= 0; i--) {
203                String relation = keyValueTokens[i];
204                String[] keyValuePair = CmsStringUtil.splitAsArray(relation, '=');
205
206                String key = keyValuePair[0];
207                String value = keyValuePair[1];
208
209                if (PARAM_KEY_CATEGORY_TYPES.equals(key)) {
210                    m_categoryTypes = CmsStringUtil.splitAsList(value, ',');
211                } else if (PARAM_KEY_RESOURCE.equals(key)) {
212                    setFileName(value);
213                } else if (PARAM_KEY_RESOURCE_TYPE.equals(key)) {
214                    I_CmsResourceType type = OpenCms.getResourceManager().getResourceType(value);
215                    if (type != null) {
216                        setType(type.getTypeId());
217                    }
218                } else if (PARAM_KEY_SORT_ASC.equals(key)) {
219                    m_sortAsc = Boolean.valueOf(value).booleanValue();
220                } else if (PARAM_KEY_SORT_BY.equals(key)) {
221                    if (value.contains(":")) {
222                        String[] keyValuePairProp = CmsStringUtil.splitAsArray(value, ':');
223                        String keyProp = keyValuePairProp[0];
224                        String valueProp = keyValuePairProp[1];
225                        m_sortBy = keyProp;
226                        m_sortByPropertyName = valueProp;
227                    } else {
228                        m_sortBy = value;
229                        m_sortByPropertyName = null;
230                    }
231                } else if (PARAM_KEY_SUB_TREE.equals(key)) {
232                    m_subTree = Boolean.valueOf(value).booleanValue();
233                } else if (PARAM_KEY_COUNT.equals(key)) {
234                    int count = 0;
235                    try {
236                        count = Integer.parseInt(value);
237                    } catch (NumberFormatException e) {
238                        // ignore
239                    }
240                    setCount(count);
241                } else if (PARAM_EXCLUDETIMERANGE.equalsIgnoreCase(key)) {
242                    setExcludeTimerange(Boolean.valueOf(value).booleanValue());
243                } else {
244                    LOG.error("Unknow key found in collector parameters.");
245                }
246            }
247        }
248    }
249
250    /** Compares the release date of resources in descending order. */
251    public static final Comparator<CmsResource> COMPARE_DATE_RELEASED_DESC = new Comparator<CmsResource>() {
252
253        /**
254         * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
255         */
256        public int compare(CmsResource r1, CmsResource r2) {
257
258            if (r1 == r2) {
259                return 0;
260            }
261
262            long date1 = r1.getDateReleased();
263            if (date1 == CmsResource.DATE_RELEASED_DEFAULT) {
264                // use creation date if release date is not set
265                date1 = r1.getDateLastModified();
266            }
267
268            long date2 = r2.getDateReleased();
269            if (date2 == CmsResource.DATE_RELEASED_DEFAULT) {
270                // use creation date if release date is not set
271                date2 = r2.getDateLastModified();
272            }
273
274            return (date1 > date2) ? 1 : (date1 < date2) ? -1 : 0;
275        }
276    };
277
278    /** The log object for this class. */
279    protected static final Log LOG = CmsLog.getLog(CmsCategoryResourceCollector.class);
280
281    /** Static array of the collectors implemented by this class. */
282    private static final String[] COLLECTORS = {"allKeyValuePairFiltered"};
283
284    /** Array list for fast collector name lookup. */
285    private static final List<String> COLLECTORS_LIST = Collections.unmodifiableList(Arrays.asList(COLLECTORS));
286
287    /**
288     * @see org.opencms.file.collectors.I_CmsResourceCollector#getCollectorNames()
289     */
290    public List<String> getCollectorNames() {
291
292        return COLLECTORS_LIST;
293    }
294
295    /**
296     * @see org.opencms.file.collectors.I_CmsResourceCollector#getCreateLink(org.opencms.file.CmsObject, java.lang.String, java.lang.String)
297     */
298    public String getCreateLink(CmsObject cms, String collectorName, String param)
299    throws CmsException, CmsDataAccessException {
300
301        // if action is not set, use default action
302        if (collectorName == null) {
303            collectorName = COLLECTORS[0];
304        }
305
306        switch (COLLECTORS_LIST.indexOf(collectorName)) {
307            case 0:
308                // "allKeyValuePairFiltered"
309                return null;
310            default:
311                throw new CmsDataAccessException(
312                    Messages.get().container(Messages.ERR_COLLECTOR_NAME_INVALID_1, collectorName));
313        }
314    }
315
316    /**
317     * @see org.opencms.file.collectors.I_CmsResourceCollector#getCreateParam(org.opencms.file.CmsObject, java.lang.String, java.lang.String)
318     */
319    public String getCreateParam(CmsObject cms, String collectorName, String param) throws CmsDataAccessException {
320
321        // if action is not set, use default action
322        if (collectorName == null) {
323            collectorName = COLLECTORS[0];
324        }
325
326        switch (COLLECTORS_LIST.indexOf(collectorName)) {
327            case 0:
328                // "allKeyValuePairFiltered"
329                return null;
330            default:
331                throw new CmsDataAccessException(
332                    Messages.get().container(Messages.ERR_COLLECTOR_NAME_INVALID_1, collectorName));
333        }
334    }
335
336    /**
337     * @see org.opencms.file.collectors.I_CmsResourceCollector#getResults(org.opencms.file.CmsObject, java.lang.String, java.lang.String)
338     */
339    public List<CmsResource> getResults(CmsObject cms, String collectorName, String param)
340    throws CmsDataAccessException, CmsException {
341
342        return getResults(cms, collectorName, param, -1);
343    }
344
345    /**
346     * @see org.opencms.file.collectors.I_CmsResourceCollector#getResults(org.opencms.file.CmsObject, java.lang.String, java.lang.String)
347     */
348    public List<CmsResource> getResults(CmsObject cms, String collectorName, String param, int numResults)
349    throws CmsDataAccessException, CmsException {
350
351        // if action is not set use default
352        if (collectorName == null) {
353            collectorName = COLLECTORS[0];
354        }
355
356        switch (COLLECTORS_LIST.indexOf(collectorName)) {
357
358            case 0:
359                // "allKeyValuePairFiltered"
360                return allKeyValuePairFiltered(cms, param, numResults);
361            default:
362                throw new CmsDataAccessException(
363                    Messages.get().container(Messages.ERR_COLLECTOR_NAME_INVALID_1, collectorName));
364        }
365    }
366
367    /**
368     * Collects all resources for the given categories filtered and sorted by the given collector parameter.<p>
369     *
370     * @param cms the current OpenCms user context
371     * @param param value parameter to filter the resources
372     * @param numResults number of results
373     *
374     * @return a list of resources filtered and sorted by the given collector parameter
375     *
376     * @throws CmsException if something goes wrong
377     */
378    protected List<CmsResource> allKeyValuePairFiltered(CmsObject cms, String param, int numResults)
379    throws CmsException {
380
381        CmsCategoryCollectorData data = new CmsCategoryCollectorData(param);
382
383        if ((data.getCategoryTypes() != null) && (data.getCategoryTypes().size() > 0)) {
384            List<CmsResource> result = new ArrayList<CmsResource>();
385            Map<String, List<CmsResource>> sortCategories = new HashMap<String, List<CmsResource>>();
386            String foldername = null;
387            boolean includeSubTree = false;
388            if (data.getFileName() != null) {
389                foldername = CmsResource.getFolderPath(data.getFileName());
390                includeSubTree = data.isSubTree();
391            } else {
392                foldername = "/";
393                includeSubTree = true;
394            }
395
396            CmsResourceFilter filter = CmsResourceFilter.DEFAULT.addExcludeFlags(CmsResource.FLAG_TEMPFILE);
397            if (data.getType() != -1) {
398                filter = filter.addRequireType(data.getType());
399            }
400            if (data.isExcludeTimerange() && !cms.getRequestContext().getCurrentProject().isOnlineProject()) {
401                // include all not yet released and expired resources in an offline project
402                filter = filter.addExcludeTimerange();
403            }
404
405            List<CmsResource> resources = cms.readResources(foldername, filter, includeSubTree);
406            List<String> categoryTypes = data.getCategoryTypes();
407            Iterator<CmsResource> itResources = resources.iterator();
408            CmsResource resource;
409            CmsCategoryService service = CmsCategoryService.getInstance();
410            while (itResources.hasNext()) {
411                resource = itResources.next();
412                Iterator<CmsCategory> itCategories = service.readResourceCategories(cms, resource).iterator();
413                while (itCategories.hasNext()) {
414                    CmsCategory category = itCategories.next();
415                    if (categoryTypes.contains(category.getPath())) {
416                        if ((data.getSortBy() != null) && data.getSortBy().equals("category")) {
417                            if (sortCategories.containsKey(category.getPath())) {
418                                (sortCategories.get(category.getPath())).add(resource);
419                            } else {
420                                List<CmsResource> sortResources = new ArrayList<CmsResource>();
421                                sortResources.add(resource);
422                                sortCategories.put(category.getPath(), sortResources);
423                            }
424                        } else {
425                            if (!result.contains(resource)) {
426                                result.add(resource);
427                            }
428                        }
429                    }
430                }
431            }
432
433            if ((data.getSortBy() != null) && data.getSortBy().equals("date")) {
434                if (!data.isSortAsc()) {
435                    Collections.sort(result, COMPARE_DATE_RELEASED_DESC);
436                } else {
437                    Collections.sort(result, I_CmsResource.COMPARE_DATE_RELEASED);
438                }
439            } else if ((data.getSortBy() != null) && data.getSortBy().equals("category")) {
440                // categories are sort by their paths
441                Iterator<String> itCategoryTypes = categoryTypes.iterator();
442                while (itCategoryTypes.hasNext()) {
443                    List<CmsResource> categoryListToAdd = sortCategories.get(itCategoryTypes.next());
444                    if (categoryListToAdd != null) {
445                        result.addAll(categoryListToAdd);
446                    }
447                }
448            } else if ((data.getSortBy() != null) && data.getSortBy().equals("property")) {
449                Comparator<CmsResource> comp = new CmsPropertyResourceComparator(
450                    cms,
451                    data.getSortByPropertyName(),
452                    data.getSortOrder());
453                Collections.sort(result, comp);
454            }
455
456            return shrinkToFit(result, data.getCount(), numResults);
457        }
458        return null;
459    }
460}