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, 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.ade.publish; 029 030import org.opencms.main.CmsLog; 031 032import java.util.ArrayList; 033import java.util.Calendar; 034import java.util.Collections; 035import java.util.Comparator; 036import java.util.Date; 037import java.util.HashMap; 038import java.util.List; 039import java.util.Locale; 040import java.util.Map; 041 042import org.apache.commons.logging.Log; 043 044/** 045 * Helper class for splitting a publish list into publish groups.<p> 046 * 047 * @param <RESOURCE> the resource class type 048 * @param <GROUP> the resource group class type 049 * 050 * @since 8.0.0 051 */ 052public abstract class A_CmsPublishGroupHelper<RESOURCE, GROUP> { 053 054 /** An enum representing the age of a publish list resource. */ 055 public enum GroupAge { 056 /** group age constant. */ 057 medium, 058 /** group age constant. */ 059 old, 060 /** group age constant. */ 061 young 062 } 063 064 /** 065 * Comparator used for sorting publish resources.<p> 066 */ 067 public class SortingComparator implements Comparator<RESOURCE> { 068 069 /** 070 * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object) 071 */ 072 public int compare(RESOURCE r1, RESOURCE r2) { 073 074 if (r1 == r2) { 075 return 0; 076 } 077 078 long date1 = getDateLastModified(r1); 079 long date2 = getDateLastModified(r2); 080 081 return (date1 > date2) ? -1 : (date1 < date2) ? 1 : 0; 082 } 083 } 084 085 /** The gap between session groups. */ 086 protected static final int GROUP_SESSIONS_GAP = 8 * 60 * 60 * 1000; 087 088 /** The log instance for this class. */ 089 private static final Log LOG = CmsLog.getLog(A_CmsPublishGroupHelper.class); 090 091 /** The current locale. */ 092 private Locale m_locale; 093 094 /** 095 * Creates a new publish group helper for a given locale.<p> 096 * 097 * @param locale the locale to use 098 */ 099 public A_CmsPublishGroupHelper(Locale locale) { 100 101 m_locale = locale; 102 } 103 104 /** 105 * Given a descending list of dates represented as longs, this method computes a map from the dates 106 * to their age in (local) days.<p> 107 * 108 * @param sortedDates a descending list of dates represented as longs 109 * 110 * @return a map from dates to ages (measured in days) 111 */ 112 public Map<Long, Integer> computeDays(List<Long> sortedDates) { 113 114 if (sortedDates.isEmpty()) { 115 return Collections.<Long, Integer> emptyMap(); 116 } 117 Map<Long, Integer> days = new HashMap<Long, Integer>(); 118 long lastDate = System.currentTimeMillis(); 119 int dayCounter = 0; 120 for (Long dateObj : sortedDates) { 121 long date = dateObj.longValue(); 122 long dayDifference = getDayDifference(lastDate, date); 123 dayCounter += dayDifference; 124 lastDate = date; 125 days.put(dateObj, Integer.valueOf(dayCounter)); 126 } 127 return days; 128 } 129 130 /** 131 * Computes a map from modification date to number of (local) days since the modification date.<p> 132 * 133 * @param resources a list of resources 134 * 135 * @return a map from modification dates to the number of days since the modification date 136 */ 137 public Map<Long, Integer> computeDaysForResources(List<RESOURCE> resources) { 138 139 Map<Long, Integer> result = computeDays(getModificationDates(resources)); 140 if (LOG.isDebugEnabled()) { 141 for (RESOURCE res : resources) { 142 LOG.debug( 143 "Resource " 144 + getRootPath(res) 145 + " is " 146 + result.get(Long.valueOf(getDateLastModified(res))) 147 + " days old."); 148 } 149 } 150 return result; 151 } 152 153 /** 154 * Gets the difference in days between to dates given as longs.<p> 155 * 156 * The first date must be later than the second date. 157 * 158 * @param first the first date 159 * @param second the second date 160 * 161 * @return the difference between the two dates in days 162 */ 163 public int getDayDifference(long first, long second) { 164 165 Calendar firstDay = getStartOfDay(first); 166 Calendar secondDay = getStartOfDay(second); 167 int result = 0; 168 if (first >= second) { 169 while (firstDay.after(secondDay)) { 170 firstDay.add(Calendar.DAY_OF_MONTH, -1); 171 result += 1; 172 } 173 } else { 174 while (secondDay.after(firstDay)) { 175 secondDay.add(Calendar.DAY_OF_MONTH, -1); 176 result -= 1; 177 } 178 } 179 return result; 180 } 181 182 /** 183 * Splits a list of resources into groups.<p> 184 * 185 * @param resources the list of resources 186 * 187 * @return the list of groups 188 */ 189 public List<GROUP> getGroups(List<RESOURCE> resources) { 190 191 List<RESOURCE> sortedResources = new ArrayList<RESOURCE>(resources); 192 Collections.sort(sortedResources, new SortingComparator()); 193 Map<Long, Integer> daysMap = computeDaysForResources(sortedResources); 194 Map<GroupAge, List<RESOURCE>> resourcesByAge = partitionPublishResourcesByAge(sortedResources, daysMap); 195 List<List<RESOURCE>> youngGroups = partitionYoungResources(resourcesByAge.get(GroupAge.young)); 196 List<List<RESOURCE>> mediumGroups = partitionMediumResources(resourcesByAge.get(GroupAge.medium), daysMap); 197 List<RESOURCE> oldGroup = resourcesByAge.get(GroupAge.old); 198 List<GROUP> resultGroups = new ArrayList<GROUP>(); 199 for (List<RESOURCE> groupRes : youngGroups) { 200 String name = getPublishGroupName(groupRes, GroupAge.young); 201 resultGroups.add(createGroup(name, groupRes)); 202 } 203 for (List<RESOURCE> groupRes : mediumGroups) { 204 String name = getPublishGroupName(groupRes, GroupAge.medium); 205 resultGroups.add(createGroup(name, groupRes)); 206 } 207 if (!oldGroup.isEmpty()) { 208 String oldName = getPublishGroupName(oldGroup, GroupAge.old); 209 resultGroups.add(createGroup(oldName, oldGroup)); 210 } 211 return resultGroups; 212 } 213 214 /** 215 * Given a list of resources, this method returns a list of their modification dates.<p> 216 * 217 * @param resources a list of resources 218 * 219 * @return the modification dates of the resources, in the same order as the resources 220 */ 221 public List<Long> getModificationDates(List<RESOURCE> resources) { 222 223 List<Long> result = new ArrayList<Long>(); 224 for (RESOURCE res : resources) { 225 result.add(Long.valueOf(getDateLastModified(res))); 226 } 227 return result; 228 } 229 230 /** 231 * Returns the localized name for a given publish group based on its age.<p> 232 * 233 * @param resources the resources of the publish group 234 * @param age the age of the publish group 235 * 236 * @return the localized name of the publish group 237 */ 238 public String getPublishGroupName(List<RESOURCE> resources, GroupAge age) { 239 240 long groupDate = getDateLastModified(resources.get(0)); 241 String groupName; 242 switch (age) { 243 case young: 244 groupName = Messages.get().getBundle(m_locale).key( 245 Messages.GUI_GROUPNAME_SESSION_1, 246 new Date(groupDate)); 247 248 break; 249 case medium: 250 groupName = Messages.get().getBundle(m_locale).key(Messages.GUI_GROUPNAME_DAY_1, new Date(groupDate)); 251 break; 252 case old: 253 default: 254 groupName = Messages.get().getBundle(m_locale).key(Messages.GUI_GROUPNAME_EVERYTHING_ELSE_0); 255 break; 256 } 257 return groupName; 258 } 259 260 /** 261 * Returns a calendar object representing the start of the day in which a given time lies.<p> 262 * 263 * @param time a long representing a time 264 * 265 * @return a calendar object which represents the day in which the time lies 266 */ 267 public Calendar getStartOfDay(long time) { 268 269 Calendar cal = Calendar.getInstance(); 270 cal.setTimeInMillis(time); 271 int year = cal.get(Calendar.YEAR); 272 int month = cal.get(Calendar.MONTH); 273 int day = cal.get(Calendar.DAY_OF_MONTH); 274 Calendar result = Calendar.getInstance(); 275 result.set(Calendar.YEAR, year); 276 result.set(Calendar.MONTH, month); 277 result.set(Calendar.DAY_OF_MONTH, day); 278 return result; 279 } 280 281 /** 282 * Computes publish groups for a list of resources with age "medium".<p> 283 * 284 * @param resources the list of resources 285 * @param days a map from modification dates to the number of days since the modification 286 * 287 * @return a list of publish groups 288 */ 289 public List<List<RESOURCE>> partitionMediumResources(List<RESOURCE> resources, Map<Long, Integer> days) { 290 291 if (resources.isEmpty()) { 292 return Collections.<List<RESOURCE>> emptyList(); 293 } 294 RESOURCE firstRes = resources.get(0); 295 int lastDay = days.get(Long.valueOf(getDateLastModified(firstRes))).intValue(); 296 List<List<RESOURCE>> result = new ArrayList<List<RESOURCE>>(); 297 List<RESOURCE> currentGroup = new ArrayList<RESOURCE>(); 298 result.add(currentGroup); 299 for (RESOURCE res : resources) { 300 LOG.debug("Processing medium-aged resource " + getRootPath(res)); 301 int day = days.get(Long.valueOf(getDateLastModified(res))).intValue(); 302 if (day != lastDay) { 303 LOG.debug("=== new group ==="); 304 currentGroup = new ArrayList<RESOURCE>(); 305 result.add(currentGroup); 306 } 307 lastDay = day; 308 currentGroup.add(res); 309 } 310 return result; 311 } 312 313 /** 314 * Partitions a list of resources by their age in (local) days since the last modification.<p> 315 * 316 * @param resources the list of resources to partition 317 * @param days the map from modification dates to the number of (local) days since the modification 318 * 319 * @return a map from age enum values to the list of resources which fall into the corresponding age group 320 */ 321 public Map<GroupAge, List<RESOURCE>> partitionPublishResourcesByAge( 322 List<RESOURCE> resources, 323 Map<Long, Integer> days) { 324 325 List<RESOURCE> youngRes = new ArrayList<RESOURCE>(); 326 List<RESOURCE> mediumRes = new ArrayList<RESOURCE>(); 327 List<RESOURCE> oldRes = new ArrayList<RESOURCE>(); 328 for (RESOURCE res : resources) { 329 int day = days.get(Long.valueOf(getDateLastModified(res))).intValue(); 330 List<RESOURCE> listToAddTo = null; 331 if (day < 7) { 332 listToAddTo = youngRes; 333 LOG.debug("Classifying publish resource " + getRootPath(res) + " as young"); 334 } else if (day < 28) { 335 listToAddTo = mediumRes; 336 LOG.debug("Classifying publish resource " + getRootPath(res) + " as medium-aged"); 337 } else { 338 listToAddTo = oldRes; 339 LOG.debug("Classifying publish resource " + getRootPath(res) + " as old"); 340 } 341 listToAddTo.add(res); 342 } 343 Map<GroupAge, List<RESOURCE>> result = new HashMap<GroupAge, List<RESOURCE>>(); 344 result.put(GroupAge.young, youngRes); 345 result.put(GroupAge.medium, mediumRes); 346 result.put(GroupAge.old, oldRes); 347 return result; 348 } 349 350 /** 351 * Partitions the list of young resources into publish groups.<p> 352 * 353 * @param resources the list of resources to partition 354 * 355 * @return a partition of the resources into publish groups 356 */ 357 public List<List<RESOURCE>> partitionYoungResources(List<RESOURCE> resources) { 358 359 if (resources.isEmpty()) { 360 return Collections.<List<RESOURCE>> emptyList(); 361 } 362 List<List<RESOURCE>> result = new ArrayList<List<RESOURCE>>(); 363 List<RESOURCE> currentGroup = new ArrayList<RESOURCE>(); 364 result.add(currentGroup); 365 366 long lastDate = getDateLastModified(resources.get(0)); 367 for (RESOURCE res : resources) { 368 LOG.debug("Processing young resource " + getRootPath(res)); 369 long resDate = getDateLastModified(res); 370 if ((lastDate - resDate) > GROUP_SESSIONS_GAP) { 371 LOG.debug("=== new group ==="); 372 currentGroup = new ArrayList<RESOURCE>(); 373 result.add(currentGroup); 374 } 375 lastDate = resDate; 376 currentGroup.add(res); 377 } 378 return result; 379 } 380 381 /** 382 * Creates a named group of resources.<p> 383 * 384 * @param name the name of the group 385 * @param resources the resources which should be put in the group 386 * 387 * @return the named group 388 */ 389 protected abstract GROUP createGroup(String name, List<RESOURCE> resources); 390 391 /** 392 * Gets the last modification date of a resource.<p> 393 * 394 * @param res the resource 395 * 396 * @return the last modification date of res 397 */ 398 protected abstract long getDateLastModified(RESOURCE res); 399 400 /** 401 * Gets the root path of a resource.<p> 402 * 403 * @param res the resource 404 * 405 * @return the root path of res 406 */ 407 protected abstract String getRootPath(RESOURCE res); 408}