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.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) throws CmsException {
174
175        Map<String, String> params = getParameters(param);
176
177        String foldername = "/";
178        if (params.containsKey(PARAM_KEY_RESOURCE)) {
179            foldername = CmsResource.getFolderPath(params.get(PARAM_KEY_RESOURCE));
180        }
181
182        long dateFrom = 0L;
183        long dateTo = Long.MAX_VALUE;
184        if (params.containsKey(PARAM_KEY_DATEFROM)) {
185            try {
186                dateFrom = Long.parseLong(params.get(PARAM_KEY_DATEFROM));
187            } catch (NumberFormatException e) {
188                // error parsing from date
189                LOG.error(
190                    Messages.get().getBundle().key(
191                        Messages.ERR_COLLECTOR_PARAM_INVALID_1,
192                        PARAM_KEY_DATEFROM + "=" + params.get(PARAM_KEY_DATEFROM)));
193                throw e;
194            }
195        }
196        if (params.containsKey(PARAM_KEY_DATETO)) {
197            try {
198                dateTo = Long.parseLong(params.get(PARAM_KEY_DATETO));
199            } catch (NumberFormatException e) {
200                // error parsing to date
201                LOG.error(
202                    Messages.get().getBundle().key(
203                        Messages.ERR_COLLECTOR_PARAM_INVALID_1,
204                        PARAM_KEY_DATETO + "=" + params.get(PARAM_KEY_DATETO)));
205                throw e;
206            }
207        }
208
209        // create the filter to read the resources
210        CmsResourceFilter filter = CmsResourceFilter.DEFAULT_FILES.addExcludeFlags(
211            CmsResource.FLAG_TEMPFILE).addRequireLastModifiedAfter(dateFrom).addRequireLastModifiedBefore(dateTo);
212
213        // check if a resource type has to be excluded
214        if (params.containsKey(PARAM_KEY_EXCLUDETYPE)) {
215            String excludeType = params.get(PARAM_KEY_EXCLUDETYPE);
216            int typeId = -1;
217            try {
218                // try to look up the resource type
219                I_CmsResourceType resourceType = OpenCms.getResourceManager().getResourceType(excludeType);
220                typeId = resourceType.getTypeId();
221            } catch (CmsLoaderException e1) {
222                // maybe the int ID is directly used?
223                try {
224                    int typeInt = Integer.parseInt(excludeType);
225                    I_CmsResourceType resourceType = OpenCms.getResourceManager().getResourceType(typeInt);
226                    typeId = resourceType.getTypeId();
227                    if (LOG.isWarnEnabled()) {
228                        LOG.warn(
229                            Messages.get().getBundle().key(
230                                Messages.LOG_RESTYPE_INTID_2,
231                                resourceType.getTypeName(),
232                                new Integer(resourceType.getTypeId())));
233                    }
234                } catch (NumberFormatException e2) {
235                    // bad number format used for type
236                    throw new CmsRuntimeException(
237                        Messages.get().container(
238                            Messages.ERR_COLLECTOR_PARAM_INVALID_1,
239                            PARAM_KEY_EXCLUDETYPE + "=" + params.get(PARAM_KEY_EXCLUDETYPE)),
240                        e2);
241                } catch (CmsLoaderException e2) {
242                    // this resource type does not exist
243                    throw new CmsRuntimeException(
244                        Messages.get().container(Messages.ERR_UNKNOWN_RESTYPE_1, excludeType),
245                        e2);
246                }
247            }
248            if (typeId != -1) {
249                filter = filter.addExcludeType(typeId);
250            }
251        }
252
253        // read the resources using the configured filter
254        List<CmsResource> result = cms.readResources(foldername, filter, tree);
255
256        // determine which attribute should be used to sort the result
257        String sortBy = CmsDateResourceComparator.DATE_ATTRIBUTES_LIST.get(1);
258        if (params.containsKey(PARAM_KEY_SORTBY)) {
259            sortBy = params.get(PARAM_KEY_SORTBY);
260        }
261        List<String> dateIdentifiers = new ArrayList<String>(1);
262        dateIdentifiers.add(sortBy);
263
264        // a special date comparator is used to sort the resources
265        CmsDateResourceComparator comparator = new CmsDateResourceComparator(cms, dateIdentifiers, asc);
266        Collections.sort(result, comparator);
267
268        int count = -1;
269        if (params.containsKey(PARAM_KEY_COUNT)) {
270            try {
271                count = Integer.parseInt(params.get(PARAM_KEY_COUNT));
272            } catch (NumberFormatException e) {
273                // error parsing the count
274                LOG.error(
275                    Messages.get().getBundle().key(
276                        Messages.ERR_COLLECTOR_PARAM_INVALID_1,
277                        PARAM_KEY_COUNT + "=" + params.get(PARAM_KEY_COUNT)));
278                throw e;
279            }
280        }
281        if ((count > 0) || (numResults > 0)) {
282            return shrinkToFit(result, count, numResults);
283        } else {
284            return result;
285        }
286    }
287
288    /**
289     * Returns the collector parameters.<p>
290     *
291     * @param param the collector parameter
292     *
293     * @return the collector parameters
294     */
295    private Map<String, String> getParameters(String param) {
296
297        if (CmsStringUtil.isNotEmpty(param)) {
298            return CmsStringUtil.splitAsMap(param, "|", "=");
299        }
300        return Collections.emptyMap();
301    }
302}