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.CmsObject;
031import org.opencms.file.CmsProperty;
032import org.opencms.file.CmsResource;
033import org.opencms.main.CmsException;
034import org.opencms.util.CmsStringUtil;
035import org.opencms.util.CmsUUID;
036
037import java.util.Arrays;
038import java.util.Collections;
039import java.util.Comparator;
040import java.util.HashMap;
041import java.util.List;
042import java.util.Map;
043
044/**
045 * Comparator for sorting resource objects based on dates.<p>
046 *
047 * The comparator can be configured to use any date based on resource attributes or properties.
048 * The user must in the constructor {@link #CmsDateResourceComparator(CmsObject, List, boolean)}
049 * provide a list of one or more <b>date identifiers</b> that should be checked, in the order they
050 * should be checked. This list of dates identifiers must be Strings which tell the comparator which dates to use.
051 * The first valid date identifier that is found for a resource is used as date for
052 * comparing this resource to other resources.<p>
053 *
054 * The following date identifiers can be used
055 * to access the corresponding value of a {@link CmsResource}:<ul>
056 * <li><code>"dateCreated"</code>, which means {@link CmsResource#getDateCreated()}.
057 * <li><code>"dateLastModified"</code>, which means {@link CmsResource#getDateLastModified()}.
058 * <li><code>"dateContent"</code>, which means {@link CmsResource#getDateContent()}.
059 * <li><code>"dateReleased"</code>, which means {@link CmsResource#getDateReleased()}.
060 * <li><code>"dateExpired"</code>, which means {@link CmsResource#getDateExpired()}.
061 * <li>Anything else will be treated as property name, so it will be attempted to read a property
062 * with that name from the resource, and convert that value to a long. Should this fail
063 * for any reason, the next entry from the list of dates will be processed.<p>
064 * <li>If no match is found at all, {@link CmsResource#getDateCreated()} is used as default.
065 * </ul>
066 *
067 * Serves as {@link java.util.Comparator} for resources and as comparator key for the resource
068 * at the same time. Uses lazy initializing of comparator keys for a resource.<p>
069 *
070 * @since 6.0.0
071 */
072public class CmsDateResourceComparator implements Comparator<CmsResource> {
073
074    /** Possible keywords to read dates from the resource attributes. */
075    private static final String[] DATE_ATTRIBUTES = {
076        "dateCreated",
077        "dateLastModified",
078        "dateContent",
079        "dateReleased",
080        "dateExpired"};
081
082    /**  Possible keywords to read dates from the resource attributes in a List. */
083    public static final List<String> DATE_ATTRIBUTES_LIST = Arrays.asList(DATE_ATTRIBUTES);
084
085    /** The date sort order. */
086    private boolean m_asc;
087
088    /** The current OpenCms user context. */
089    private CmsObject m_cms;
090
091    /** The date of this comparator key. */
092    private long m_date;
093
094    /** The list that describes the dates to check, in the order they should be checked. */
095    private List<String> m_dateIdentifiers;
096
097    /** The internal map of comparator keys. */
098    private Map<CmsUUID, CmsDateResourceComparator> m_keys;
099
100    /**
101     * Creates a new instance of this comparator key.<p>
102     *
103     * @param cms the current OpenCms user context
104     * @param dateIdentifiers the names of the dates to check
105     * @param asc if true, the date sort order is ascending, otherwise descending
106     */
107    public CmsDateResourceComparator(CmsObject cms, List<String> dateIdentifiers, boolean asc) {
108
109        m_cms = cms;
110        m_asc = asc;
111        m_dateIdentifiers = dateIdentifiers;
112        if (m_dateIdentifiers == null) {
113            m_dateIdentifiers = Collections.emptyList();
114        }
115        m_keys = new HashMap<CmsUUID, CmsDateResourceComparator>();
116    }
117
118    /**
119     * Creates a new, empty instance of this comparator key, used for the calculated map valued.<p>
120     */
121    private CmsDateResourceComparator() {
122
123        // NOOP
124    }
125
126    /**
127     * Calculates the date to use for comparison of this resource based on the given date identifiers.<p>
128     *
129     * @param cms the current OpenCms user context
130     * @param resource the resource to create the key for
131     * @param dateIdentifiers the date identifiers to use for selecting the date
132     * @param defaultValue the default value to use in case no value can be calculated
133     *
134     * @return the calculated date
135     *
136     * @see CmsDateResourceComparator for a description about how the date identifieres are used
137     */
138    public static long calculateDate(
139        CmsObject cms,
140        CmsResource resource,
141        List<String> dateIdentifiers,
142        long defaultValue) {
143
144        long result = 0;
145        List<CmsProperty> properties = null;
146        for (int i = 0, size = dateIdentifiers.size(); i < size; i++) {
147            // check all configured comparisons
148            String date = dateIdentifiers.get(i);
149            int pos = DATE_ATTRIBUTES_LIST.indexOf(date);
150            switch (pos) {
151                case 0: // "dateCreated"
152                    result = resource.getDateCreated();
153                    break;
154                case 1: // "dateLastModified"
155                    result = resource.getDateLastModified();
156                    break;
157                case 2: // "dateContent"
158                    if (resource.isFile()) {
159                        // date content makes no sense for folders
160                        result = resource.getDateContent();
161                    }
162                    break;
163                case 3: // "dateReleased"
164                    long dr = resource.getDateReleased();
165                    if (dr != CmsResource.DATE_RELEASED_DEFAULT) {
166                        // default release date must be ignored
167                        result = dr;
168                    }
169                    break;
170                case 4: // "dateExpired"
171                    long de = resource.getDateExpired();
172                    if (de != CmsResource.DATE_EXPIRED_DEFAULT) {
173                        // default expiration date must be ignored
174                        result = de;
175                    }
176                    break;
177                default:
178                    // of this is not an attribute, assume this is a property
179                    if (properties == null) {
180                        // we may not have to read the properties since the user may only use attributes,
181                        // so use lazy initializing here
182                        try {
183                            properties = cms.readPropertyObjects(resource, false);
184                        } catch (CmsException e) {
185                            // use empty list in case of an error, to avoid further re-read tries
186                            properties = Collections.emptyList();
187                        }
188                    }
189                    String propValue = CmsProperty.get(date, properties).getValue();
190                    if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(propValue)) {
191                        try {
192                            result = Long.parseLong(propValue.trim());
193                        } catch (NumberFormatException e) {
194                            // maybe we have better luck with the next property
195                        }
196                    }
197                    break;
198            }
199            if (result != 0) {
200                // if a date value has been found, terminate the loop
201                break;
202            }
203        }
204        if (result == 0) {
205            // if nothing else was found, use default
206            result = defaultValue;
207        }
208        return result;
209    }
210
211    /**
212     * Creates a new instance of this comparator key.<p>
213     *
214     * @param cms the current OpenCms user context
215     * @param resource the resource to create the key for
216     * @param dateIdentifiers the date identifiers to use for selecting the date
217     *
218     * @return a new instance of this comparator key
219     */
220    private static CmsDateResourceComparator create(CmsObject cms, CmsResource resource, List<String> dateIdentifiers) {
221
222        CmsDateResourceComparator result = new CmsDateResourceComparator();
223        result.m_date = calculateDate(cms, resource, dateIdentifiers, resource.getDateCreated());
224        return result;
225    }
226
227    /**
228     * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
229     */
230    public int compare(CmsResource res0, CmsResource res1) {
231
232        if (res0 == res1) {
233            return 0;
234        }
235
236        CmsDateResourceComparator key0 = m_keys.get(res0.getStructureId());
237        CmsDateResourceComparator key1 = m_keys.get(res1.getStructureId());
238
239        if (key0 == null) {
240            // initialize key if null
241            key0 = CmsDateResourceComparator.create(m_cms, res0, m_dateIdentifiers);
242            m_keys.put(res0.getStructureId(), key0);
243        }
244        if (key1 == null) {
245            // initialize key if null
246            key1 = CmsDateResourceComparator.create(m_cms, res1, m_dateIdentifiers);
247            m_keys.put(res1.getStructureId(), key1);
248        }
249
250        if (m_asc) {
251            // sort in ascending order
252            if (key0.m_date > key1.m_date) {
253                return 1;
254            }
255            if (key0.m_date < key1.m_date) {
256                return -1;
257            }
258        } else {
259            // sort in descending order
260            if (key0.m_date > key1.m_date) {
261                return -1;
262            }
263            if (key0.m_date < key1.m_date) {
264                return 1;
265            }
266        }
267
268        return 0;
269    }
270
271    /**
272     * Returns the date of this resource comparator key.<p>
273     *
274     * @return the date of this resource comparator key
275     */
276    public long getDate() {
277
278        return m_date;
279    }
280}