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.file.collectors;
029
030import org.opencms.db.CmsSubscriptionFilter;
031import org.opencms.db.CmsSubscriptionReadMode;
032import org.opencms.db.CmsVisitedByFilter;
033import org.opencms.file.CmsDataAccessException;
034import org.opencms.file.CmsGroup;
035import org.opencms.file.CmsObject;
036import org.opencms.file.CmsResource;
037import org.opencms.file.CmsUser;
038import org.opencms.file.I_CmsResource;
039import org.opencms.file.history.I_CmsHistoryResource;
040import org.opencms.main.CmsException;
041import org.opencms.main.CmsLog;
042import org.opencms.main.OpenCms;
043import org.opencms.util.CmsStringUtil;
044
045import java.util.ArrayList;
046import java.util.Arrays;
047import java.util.Collections;
048import java.util.Iterator;
049import java.util.List;
050import java.util.Map;
051
052import org.apache.commons.logging.Log;
053
054/**
055 * A collector that returns visited or subscribed resources depending on the current user and parameters.<p>
056 *
057 * The configuration of the collectors can be done in the parameter String using key value pairs,
058 * separated by the <code>|</code> (pipe) symbol. The following configuration options are available:<p>
059 * <ul>
060 * <li><i>currentuser</i>: determines if the current user should be used to read visited or subscribed resources
061 *     (not considered if the <code>user</code> parameter is used)</li>
062 * <li><i>daysfrom</i>: the number of days subtracted from the current day specifying the start point in time from which a resource was visited</li>
063 * <li><i>daysto</i>: the number of days subtracted from the current day specifying the end point in time to which a resource was visited</li>
064 * <li><i>groups</i>: the users group names, separated by comma, to read subscribed resources for</li>
065 * <li><i>includegroups</i>: the include groups flag to read subscribed resources also for the given or current users groups
066 *     (not considered if the <code>groups</code> parameter is used)</li>
067 * <li><i>includesubfolders</i>: the include subfolders flag to read subscribed resources also for the subfolders of the given parent folder
068 *     (not considered if the <code>resource</code> parameter is not used)</li>
069 * <li><i>mode</i>: the subscription read mode, can be <code>all</code>, <code>visited</code> or <code>unvisited</code> (default)</li>
070 * <li><i>resource</i>: the resource, i.e. the parent folder from which the subscribed or visited resources should be read from</li>
071 * <li><i>user</i>:<the user to read subscribed or visited resources for/li>
072 * </ul>
073 *
074 * Example parameter String that can be used for the collector:<br/>
075 * <code>currentuser=true|daysfrom=14|includegroups=true|mode=unvisited|resource=/demo_en/|includesubfolders=true</code><p>
076 *
077 * @since 8.0
078 */
079public class CmsSubscriptionCollector extends A_CmsResourceCollector {
080
081    /** The collector parameter key for the current user flag (to set the user in the filters to the current user). */
082    public static final String PARAM_KEY_CURRENTUSER = "currentuser";
083
084    /**
085     * The collector parameter key for the number of days subtracted from the current day
086     * specifying the start point in time from which a resource was visited.
087     */
088    public static final String PARAM_KEY_DAYSFROM = "daysfrom";
089
090    /**
091     * The collector parameter key for the number of days subtracted from the current day
092     * specifying the end point in time to which a resource was visited.<p>
093     * If the parameter {@link #PARAM_KEY_DAYSFROM} is also used, the value of this key should be less than the value
094     * set as {@link #PARAM_KEY_DAYSFROM} parameter.
095     */
096    public static final String PARAM_KEY_DAYSTO = "daysto";
097
098    /** The collector parameter key for the users group names, separated by comma, to read subscribed resources for. */
099    public static final String PARAM_KEY_GROUPS = "groups";
100
101    /** The collector parameter key for the include groups flag to read subscribed resources also for the given or current users groups. */
102    public static final String PARAM_KEY_INCLUDEGROUPS = "includegroups";
103
104    /** The collector parameter key for the include subfolders flag to read subscribed resources also for the subfolders of the given parent folder. */
105    public static final String PARAM_KEY_INCLUDESUBFOLDERS = "includesubfolders";
106
107    /** The collector parameter key for the subscription read mode. */
108    public static final String PARAM_KEY_MODE = "mode";
109
110    /** The collector parameter key for the resource, i.e. the parent folder from which the subscribed or visited resources should be read from. */
111    public static final String PARAM_KEY_RESOURCE = "resource";
112
113    /** The collector parameter key for the user to read subscribed or visited resources for. */
114    public static final String PARAM_KEY_USER = "user";
115
116    /** Static array of the collectors implemented by this class. */
117    private static final String[] COLLECTORS = {"allVisited", "allSubscribed", "allSubscribedDeleted"};
118
119    /** Array list for fast collector name lookup. */
120    private static final List<String> COLLECTORS_LIST = Collections.unmodifiableList(Arrays.asList(COLLECTORS));
121
122    /** The log object for this class. */
123    private static final Log LOG = CmsLog.getLog(CmsSubscriptionCollector.class);
124
125    /**
126     * @see org.opencms.file.collectors.I_CmsResourceCollector#getCollectorNames()
127     */
128    public List<String> getCollectorNames() {
129
130        return COLLECTORS_LIST;
131    }
132
133    /**
134     * @see org.opencms.file.collectors.I_CmsResourceCollector#getCreateLink(org.opencms.file.CmsObject, java.lang.String, java.lang.String)
135     */
136    public String getCreateLink(CmsObject cms, String collectorName, String param) {
137
138        // this collector does not support creation of new resources
139        return null;
140    }
141
142    /**
143     * @see org.opencms.file.collectors.I_CmsResourceCollector#getCreateParam(org.opencms.file.CmsObject, java.lang.String, java.lang.String)
144     */
145    public String getCreateParam(CmsObject cms, String collectorName, String param) {
146
147        // this collector does not support creation of new resources
148        return null;
149    }
150
151    /**
152     * @see org.opencms.file.collectors.I_CmsResourceCollector#getResults(org.opencms.file.CmsObject, java.lang.String, java.lang.String)
153     */
154    public List<CmsResource> getResults(CmsObject cms, String collectorName, String param)
155    throws CmsDataAccessException, CmsException {
156
157        return getResults(cms, collectorName, param, -1);
158
159    }
160
161    /**
162     * @see org.opencms.file.collectors.I_CmsResourceCollector#getResults(org.opencms.file.CmsObject, java.lang.String, java.lang.String)
163     */
164    public List<CmsResource> getResults(CmsObject cms, String collectorName, String param, int numResults)
165    throws CmsDataAccessException, CmsException {
166
167        // if action is not set use default
168        if (collectorName == null) {
169            collectorName = COLLECTORS[0];
170        }
171
172        switch (COLLECTORS_LIST.indexOf(collectorName)) {
173            case 0:
174                // "allVisited"
175                return getVisitedResources(cms, param, numResults);
176            case 1:
177                // "allSubscribed"
178                return getSubscribedResources(cms, param, numResults);
179            case 2:
180                // "allSubscribedDeleted"
181                return getSubscribedDeletedResources(cms, param, numResults);
182            default:
183                throw new CmsDataAccessException(
184                    Messages.get().container(Messages.ERR_COLLECTOR_NAME_INVALID_1, collectorName));
185        }
186    }
187
188    /**
189     * Returns the subscribed deleted resources according to the collector parameter.<p>
190     *
191     * @param cms the current users context
192     * @param param an optional collector parameter
193     * @param numResults the number of results
194     *
195     * @return the subscribed deleted resources according to the collector parameter
196     *
197     * @throws CmsException if something goes wrong
198     */
199    protected List<CmsResource> getSubscribedDeletedResources(CmsObject cms, String param, int numResults)
200    throws CmsException {
201
202        Map<String, String> params = getParameters(param);
203        CmsSubscriptionFilter filter = getSubscriptionFilter(cms, params);
204        String parentPath = filter.getParentPath();
205        if (CmsStringUtil.isNotEmpty(parentPath)) {
206            parentPath = cms.getRequestContext().removeSiteRoot(parentPath);
207        }
208
209        List<I_CmsHistoryResource> deletedResources = OpenCms.getSubscriptionManager().readSubscribedDeletedResources(
210            cms,
211            filter.getUser(),
212            Boolean.valueOf(params.get(PARAM_KEY_INCLUDEGROUPS)).booleanValue(),
213            parentPath,
214            filter.isIncludeSubFolders(),
215            filter.getFromDate());
216
217        // cast the history resources to CmsResource objects
218        List<CmsResource> result = new ArrayList<CmsResource>(deletedResources.size());
219        for (Iterator<I_CmsHistoryResource> i = deletedResources.iterator(); i.hasNext();) {
220            I_CmsHistoryResource deletedResource = i.next();
221            result.add((CmsResource)deletedResource);
222        }
223        if (numResults > 0) {
224            result = shrinkToFit(result, numResults);
225        }
226        return result;
227    }
228
229    /**
230     * Returns the subscribed resources according to the collector parameter.<p>
231     *
232     * @param cms the current users context
233     * @param param an optional collector parameter
234     * @param numResults the number of results
235     *
236     * @return the subscribed resources according to the collector parameter
237     *
238     * @throws CmsException if something goes wrong
239     */
240    protected List<CmsResource> getSubscribedResources(CmsObject cms, String param, int numResults)
241    throws CmsException {
242
243        List<CmsResource> result = OpenCms.getSubscriptionManager().readSubscribedResources(
244            cms,
245            getSubscriptionFilter(cms, param));
246        Collections.sort(result, I_CmsResource.COMPARE_DATE_LAST_MODIFIED);
247        if (numResults > 0) {
248            result = shrinkToFit(result, numResults);
249        }
250        return result;
251    }
252
253    /**
254     * Returns the configured subscription filter to use.<p>
255     *
256     * @param cms the current users context
257     * @param params the optional collector parameters
258     *
259     * @return the configured subscription filter to use
260     *
261     * @throws CmsException if something goes wrong
262     */
263    protected CmsSubscriptionFilter getSubscriptionFilter(CmsObject cms, Map<String, String> params)
264    throws CmsException {
265
266        CmsSubscriptionFilter filter = new CmsSubscriptionFilter();
267
268        // initialize the filter
269        initVisitedByFilter(filter, cms, params, false);
270
271        // set subscription filter specific parameters
272
273        // determine the mode to read subscribed resources
274        if (params.containsKey(PARAM_KEY_MODE)) {
275            String modeName = params.get(PARAM_KEY_MODE);
276            filter.setMode(CmsSubscriptionReadMode.modeForName(modeName));
277        }
278
279        // determine the groups to set in the filter
280        if (params.containsKey(PARAM_KEY_GROUPS)) {
281            List<String> groupNames = CmsStringUtil.splitAsList(params.get(PARAM_KEY_GROUPS), ',', true);
282            for (Iterator<String> i = groupNames.iterator(); i.hasNext();) {
283                String groupName = i.next();
284                try {
285                    CmsGroup group = cms.readGroup(groupName);
286                    filter.addGroup(group);
287                } catch (CmsException e) {
288                    // error reading a group
289                    LOG.error(
290                        Messages.get().getBundle().key(
291                            Messages.ERR_COLLECTOR_PARAM_INVALID_1,
292                            PARAM_KEY_GROUPS + "=" + params.get(PARAM_KEY_GROUPS)));
293                    throw e;
294                }
295            }
296        }
297        boolean includeUserGroups = Boolean.valueOf(params.get(PARAM_KEY_INCLUDEGROUPS)).booleanValue();
298        if (filter.getGroups().isEmpty() && includeUserGroups) {
299            // include the given or current users groups
300            String userName = null;
301            if (filter.getUser() != null) {
302                userName = filter.getUser().getName();
303            } else {
304                userName = cms.getRequestContext().getCurrentUser().getName();
305            }
306            filter.setGroups(cms.getGroupsOfUser(userName, false));
307        }
308
309        return filter;
310    }
311
312    /**
313     * Returns the configured subscription filter to use.<p>
314     *
315     * @param cms the current users context
316     * @param param an optional collector parameter
317     *
318     * @return the configured subscription filter to use
319     *
320     * @throws CmsException if something goes wrong
321     */
322    protected CmsSubscriptionFilter getSubscriptionFilter(CmsObject cms, String param) throws CmsException {
323
324        return getSubscriptionFilter(cms, getParameters(param));
325    }
326
327    /**
328     * Returns the configured visited by filter to use.<p>
329     *
330     * @param cms the current users context
331     * @param param an optional collector parameter
332     *
333     * @return the configured visited by filter to use
334     *
335     * @throws CmsException if something goes wrong
336     */
337    protected CmsVisitedByFilter getVisitedByFilter(CmsObject cms, String param) throws CmsException {
338
339        CmsVisitedByFilter filter = new CmsVisitedByFilter();
340        Map<String, String> params = getParameters(param);
341
342        // initialize the filter
343        initVisitedByFilter(filter, cms, params, true);
344
345        return filter;
346    }
347
348    /**
349     * Returns the visited resources according to the collector parameter.<p>
350     *
351     * @param cms the current users context
352     * @param param an optional collector parameter
353     * @param numResults the number of results
354     *
355     * @return the visited resources according to the collector parameter
356     *
357     * @throws CmsException if something goes wrong
358     */
359    protected List<CmsResource> getVisitedResources(CmsObject cms, String param, int numResults) throws CmsException {
360
361        List<CmsResource> result = OpenCms.getSubscriptionManager().readResourcesVisitedBy(
362            cms,
363            getVisitedByFilter(cms, param));
364        Collections.sort(result, I_CmsResource.COMPARE_DATE_LAST_MODIFIED);
365        if (numResults > 0) {
366            result = shrinkToFit(result, numResults);
367        }
368        return result;
369    }
370
371    /**
372     * Returns the calculated time with the days delta using the base time.<p>
373     *
374     * @param baseTime the base time to calculate the returned time from
375     * @param deltaDays the number of days which should be subtracted from the base time
376     * @param key the parameter key name used for error messages
377     * @param defaultTime the default time is used if there were errors calculating the resulting time
378     *
379     * @return the calculated time
380     */
381    private long getCalculatedTime(long baseTime, String deltaDays, String key, long defaultTime) {
382
383        try {
384            long days = Long.parseLong(deltaDays);
385            long delta = 1000L * 60L * 60L * 24L * days;
386            long result = baseTime - delta;
387            if (result >= 0) {
388                // result is a valid time stamp
389                return result;
390            }
391        } catch (NumberFormatException e) {
392            LOG.error(Messages.get().getBundle().key(Messages.ERR_COLLECTOR_PARAM_INVALID_1, key + "=" + deltaDays));
393        }
394        return defaultTime;
395    }
396
397    /**
398     * Returns the collector parameters.<p>
399     *
400     * @param param the collector parameter
401     *
402     * @return the collector parameters
403     */
404    private Map<String, String> getParameters(String param) {
405
406        if (CmsStringUtil.isNotEmpty(param)) {
407            return CmsStringUtil.splitAsMap(param, "|", "=");
408        }
409        return Collections.emptyMap();
410    }
411
412    /**
413     * Initializes the visited by filter from the parameters.<p>
414     *
415     * @param filter the filter to initialize
416     * @param cms the current users context
417     * @param params the collector parameters to configure the filter
418     * @param forceSetUser flag to determine if a user has to be set in the filter
419     *        (should be <code>true</code> for the visited by filter, <code>false</code> for the subscription filter)
420     *
421     * @throws CmsException if something goes wrong
422     */
423    private void initVisitedByFilter(
424        CmsVisitedByFilter filter,
425        CmsObject cms,
426        Map<String, String> params,
427        boolean forceSetUser) throws CmsException {
428
429        // determine the user to set in the filter
430        if (params.containsKey(PARAM_KEY_USER)) {
431            try {
432                CmsUser user = cms.readUser(params.get(PARAM_KEY_USER));
433                filter.setUser(user);
434            } catch (CmsException e) {
435                LOG.error(
436                    Messages.get().getBundle().key(Messages.ERR_COLLECTOR_PARAM_USER_1, params.get(PARAM_KEY_USER)));
437                throw e;
438            }
439        }
440        boolean setCurrentUser = Boolean.valueOf(params.get(PARAM_KEY_CURRENTUSER)).booleanValue();
441        if ((filter.getUser() == null) && (forceSetUser || setCurrentUser)) {
442            // set current user
443            filter.setUser(cms.getRequestContext().getCurrentUser());
444        }
445
446        // determine the time stamps to set in the filter
447        long currentTime = System.currentTimeMillis();
448        if (params.containsKey(PARAM_KEY_DAYSFROM)) {
449            filter.setFromDate(getCalculatedTime(currentTime, params.get(PARAM_KEY_DAYSFROM), PARAM_KEY_DAYSFROM, 0L));
450        }
451        if (params.containsKey(PARAM_KEY_DAYSTO)) {
452            filter.setToDate(
453                getCalculatedTime(currentTime, params.get(PARAM_KEY_DAYSTO), PARAM_KEY_DAYSTO, Long.MAX_VALUE));
454        }
455
456        // determine if a parent folder should be used
457        if (params.containsKey(PARAM_KEY_RESOURCE)) {
458            try {
459                CmsResource resource = cms.readResource(params.get(PARAM_KEY_RESOURCE));
460                filter.setParentResource(resource);
461                // check if the sub folders should be included
462                if (params.containsKey(PARAM_KEY_INCLUDESUBFOLDERS)) {
463                    boolean includeSubFolders = Boolean.valueOf(params.get(PARAM_KEY_INCLUDESUBFOLDERS)).booleanValue();
464                    filter.setIncludeSubfolders(includeSubFolders);
465                }
466            } catch (CmsException e) {
467                LOG.error(
468                    Messages.get().getBundle().key(
469                        Messages.ERR_COLLECTOR_PARAM_INVALID_1,
470                        PARAM_KEY_RESOURCE + "=" + params.get(PARAM_KEY_RESOURCE)));
471                throw e;
472            }
473        }
474    }
475
476}