001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (https://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: https://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: https://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.types.I_CmsResourceType;
035import org.opencms.loader.CmsLoaderException;
036import org.opencms.main.CmsException;
037import org.opencms.main.CmsLog;
038import org.opencms.main.CmsRuntimeException;
039import org.opencms.main.OpenCms;
040import org.opencms.util.CmsStringUtil;
041
042import java.util.ArrayList;
043import java.util.Arrays;
044import java.util.Collections;
045import java.util.List;
046import java.util.Map;
047
048import org.apache.commons.logging.Log;
049
050/**
051 * A resource collector that collects resources changed in a given time frame and supports flexible sorting based on resource dates.<p>
052 *
053 * @since 8.0
054 */
055public class CmsChangedResourceCollector extends A_CmsResourceCollector {
056
057    /** The collector parameter key for the maximum number of resources to return. */
058    public static final String PARAM_KEY_COUNT = "count";
059
060    /** The collector parameter key for the date from which a resource should be changed. */
061    public static final String PARAM_KEY_DATEFROM = "datefrom";
062
063    /** The collector parameter key for the date to which a resource should be changed. */
064    public static final String PARAM_KEY_DATETO = "dateto";
065
066    /** The collector parameter key for the name of the resource type to exclude from the result. */
067    public static final String PARAM_KEY_EXCLUDETYPE = "excludetype";
068
069    /** The collector parameter key for the resource, i.e. the parent folder from which the subscribed or visited resources should be read from. */
070    public static final String PARAM_KEY_RESOURCE = "resource";
071
072    /** The collector parameter key for the sort attribute that should be used to sort the result. */
073    public static final String PARAM_KEY_SORTBY = "sortby";
074
075    /** Static array of the collectors implemented by this class. */
076    private static final String[] COLLECTORS = {
077        "allChangedInFolderDateDesc",
078        "allChangedInFolderDateAsc",
079        "allChangedInSubTreeDateDesc",
080        "allChangedInSubTreeDateAsc"};
081
082    /** Array list for fast collector name lookup. */
083    private static final List<String> COLLECTORS_LIST = Collections.unmodifiableList(Arrays.asList(COLLECTORS));
084
085    /** The log object for this class. */
086    private static final Log LOG = CmsLog.getLog(CmsChangedResourceCollector.class);
087
088    /**
089     * @see org.opencms.file.collectors.I_CmsResourceCollector#getCollectorNames()
090     */
091    public List<String> getCollectorNames() {
092
093        return COLLECTORS_LIST;
094    }
095
096    /**
097     * @see org.opencms.file.collectors.I_CmsResourceCollector#getCreateLink(org.opencms.file.CmsObject, java.lang.String, java.lang.String)
098     */
099    public String getCreateLink(CmsObject cms, String collectorName, String param) {
100
101        // this collector does not support creation of new resources
102        return null;
103    }
104
105    /**
106     * @see org.opencms.file.collectors.I_CmsResourceCollector#getCreateParam(org.opencms.file.CmsObject, java.lang.String, java.lang.String)
107     */
108    public String getCreateParam(CmsObject cms, String collectorName, String param) {
109
110        // this collector does not support creation of new resources
111        return null;
112    }
113
114    /**
115     * @see org.opencms.file.collectors.I_CmsResourceCollector#getResults(org.opencms.file.CmsObject, java.lang.String, java.lang.String)
116     */
117    public List<CmsResource> getResults(CmsObject cms, String collectorName, String param)
118    throws CmsDataAccessException, CmsException {
119
120        return getResults(cms, collectorName, param, -1);
121    }
122
123    /**
124     * @see org.opencms.file.collectors.I_CmsResourceCollector#getResults(org.opencms.file.CmsObject, java.lang.String, java.lang.String)
125     */
126    public List<CmsResource> getResults(CmsObject cms, String collectorName, String param, int numResults)
127    throws CmsDataAccessException, CmsException {
128
129        // if action is not set use default
130        if (collectorName == null) {
131            collectorName = COLLECTORS[0];
132        }
133
134        switch (COLLECTORS_LIST.indexOf(collectorName)) {
135            case 0:
136                // "allChangedInFolderDateDesc"
137                return allChangedInFolderDate(cms, param, false, false, numResults);
138            case 1:
139                // "allChangedInFolderDateAsc"
140                return allChangedInFolderDate(cms, param, false, true, numResults);
141            case 2:
142                // "allChangedInSubTreeDateDesc"
143                return allChangedInFolderDate(cms, param, true, false, numResults);
144            case 3:
145                // "allChangedInSubTreeDateAsc"
146                return allChangedInFolderDate(cms, param, true, true, numResults);
147            default:
148                throw new CmsDataAccessException(
149                    Messages.get().container(Messages.ERR_COLLECTOR_NAME_INVALID_1, collectorName));
150        }
151    }
152
153    /**
154     * Returns a List of all changed resources in the folder pointed to by the parameter
155     * sorted by the date attributes specified in the parameter.<p>
156     *
157     * @param cms the current CmsObject
158     * @param param must contain an extended collector parameter set as described by {@link CmsExtendedCollectorData}
159     * @param tree if true, look in folder and all child folders, if false, look only in given folder
160     * @param asc if <code>true</code>, the sort is ascending (old dates first), otherwise it is descending
161     *      (new dates first)
162     * @param numResults number of results
163     *
164     * @return a List of all resources in the folder pointed to by the parameter sorted by the selected dates
165     *
166     * @throws CmsException if something goes wrong
167     */
168    protected List<CmsResource> allChangedInFolderDate(
169        CmsObject cms,
170        String param,
171        boolean tree,
172        boolean asc,
173        int numResults)
174    throws CmsException {
175
176        Map<String, String> params = getParameters(param);
177
178        String foldername = "/";
179        if (params.containsKey(PARAM_KEY_RESOURCE)) {
180            foldername = CmsResource.getFolderPath(params.get(PARAM_KEY_RESOURCE));
181        }
182
183        long dateFrom = 0L;
184        long dateTo = Long.MAX_VALUE;
185        if (params.containsKey(PARAM_KEY_DATEFROM)) {
186            try {
187                dateFrom = Long.parseLong(params.get(PARAM_KEY_DATEFROM));
188            } catch (NumberFormatException e) {
189                // error parsing from date
190                LOG.error(
191                    Messages.get().getBundle().key(
192                        Messages.ERR_COLLECTOR_PARAM_INVALID_1,
193                        PARAM_KEY_DATEFROM + "=" + params.get(PARAM_KEY_DATEFROM)));
194                throw e;
195            }
196        }
197        if (params.containsKey(PARAM_KEY_DATETO)) {
198            try {
199                dateTo = Long.parseLong(params.get(PARAM_KEY_DATETO));
200            } catch (NumberFormatException e) {
201                // error parsing to date
202                LOG.error(
203                    Messages.get().getBundle().key(
204                        Messages.ERR_COLLECTOR_PARAM_INVALID_1,
205                        PARAM_KEY_DATETO + "=" + params.get(PARAM_KEY_DATETO)));
206                throw e;
207            }
208        }
209
210        // create the filter to read the resources
211        CmsResourceFilter filter = CmsResourceFilter.DEFAULT_FILES.addExcludeFlags(
212            CmsResource.FLAG_TEMPFILE).addRequireLastModifiedAfter(dateFrom).addRequireLastModifiedBefore(dateTo);
213
214        // check if a resource type has to be excluded
215        if (params.containsKey(PARAM_KEY_EXCLUDETYPE)) {
216            String excludeType = params.get(PARAM_KEY_EXCLUDETYPE);
217            int typeId = -1;
218            try {
219                // try to look up the resource type
220                I_CmsResourceType resourceType = OpenCms.getResourceManager().getResourceType(excludeType);
221                typeId = resourceType.getTypeId();
222            } catch (CmsLoaderException e1) {
223                // maybe the int ID is directly used?
224                try {
225                    int typeInt = Integer.parseInt(excludeType);
226                    I_CmsResourceType resourceType = OpenCms.getResourceManager().getResourceType(typeInt);
227                    typeId = resourceType.getTypeId();
228                    if (LOG.isWarnEnabled()) {
229                        LOG.warn(
230                            Messages.get().getBundle().key(
231                                Messages.LOG_RESTYPE_INTID_2,
232                                resourceType.getTypeName(),
233                                Integer.valueOf(resourceType.getTypeId())));
234                    }
235                } catch (NumberFormatException e2) {
236                    // bad number format used for type
237                    throw new CmsRuntimeException(
238                        Messages.get().container(
239                            Messages.ERR_COLLECTOR_PARAM_INVALID_1,
240                            PARAM_KEY_EXCLUDETYPE + "=" + params.get(PARAM_KEY_EXCLUDETYPE)),
241                        e2);
242                } catch (CmsLoaderException e2) {
243                    // this resource type does not exist
244                    throw new CmsRuntimeException(
245                        Messages.get().container(Messages.ERR_UNKNOWN_RESTYPE_1, excludeType),
246                        e2);
247                }
248            }
249            if (typeId != -1) {
250                filter = filter.addExcludeType(typeId);
251            }
252        }
253
254        // read the resources using the configured filter
255        List<CmsResource> result = cms.readResources(foldername, filter, tree);
256
257        // determine which attribute should be used to sort the result
258        String sortBy = CmsDateResourceComparator.DATE_ATTRIBUTES_LIST.get(1);
259        if (params.containsKey(PARAM_KEY_SORTBY)) {
260            sortBy = params.get(PARAM_KEY_SORTBY);
261        }
262        List<String> dateIdentifiers = new ArrayList<String>(1);
263        dateIdentifiers.add(sortBy);
264
265        // a special date comparator is used to sort the resources
266        CmsDateResourceComparator comparator = new CmsDateResourceComparator(cms, dateIdentifiers, asc);
267        Collections.sort(result, comparator);
268
269        int count = -1;
270        if (params.containsKey(PARAM_KEY_COUNT)) {
271            try {
272                count = Integer.parseInt(params.get(PARAM_KEY_COUNT));
273            } catch (NumberFormatException e) {
274                // error parsing the count
275                LOG.error(
276                    Messages.get().getBundle().key(
277                        Messages.ERR_COLLECTOR_PARAM_INVALID_1,
278                        PARAM_KEY_COUNT + "=" + params.get(PARAM_KEY_COUNT)));
279                throw e;
280            }
281        }
282        if ((count > 0) || (numResults > 0)) {
283            return shrinkToFit(result, count, numResults);
284        } else {
285            return result;
286        }
287    }
288
289    /**
290     * Returns the collector parameters.<p>
291     *
292     * @param param the collector parameter
293     *
294     * @return the collector parameters
295     */
296    private Map<String, String> getParameters(String param) {
297
298        if (CmsStringUtil.isNotEmpty(param)) {
299            return CmsStringUtil.splitAsMap(param, "|", "=");
300        }
301        return Collections.emptyMap();
302    }
303}