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.notification; 029 030import org.opencms.db.CmsDbEntryNotFoundException; 031import org.opencms.db.CmsUserSettings; 032import org.opencms.file.CmsObject; 033import org.opencms.file.CmsPropertyDefinition; 034import org.opencms.file.CmsResource; 035import org.opencms.file.CmsResourceFilter; 036import org.opencms.file.CmsUser; 037import org.opencms.i18n.CmsLocaleManager; 038import org.opencms.main.CmsException; 039import org.opencms.main.CmsLog; 040import org.opencms.main.OpenCms; 041import org.opencms.util.CmsStringUtil; 042 043import java.util.ArrayList; 044import java.util.Calendar; 045import java.util.Collection; 046import java.util.Date; 047import java.util.GregorianCalendar; 048import java.util.HashMap; 049import java.util.Iterator; 050import java.util.List; 051import java.util.Map; 052import java.util.TimeZone; 053 054import org.apache.commons.logging.Log; 055import org.apache.commons.mail.EmailException; 056 057/** 058 * The basic class for the content notification feature in OpenCms. Collects all resources that require a notification, 059 * creates and sends notifications to their responsible users.<p/> 060 * 061 */ 062public class CmsNotificationCandidates { 063 064 /** The log object for this class. */ 065 private static final Log LOG = CmsLog.getLog(CmsNotificationCandidates.class); 066 067 /** the CmsObject. */ 068 private CmsObject m_cms; 069 070 /** The resources which come into question for notifications of responsible users. */ 071 private List<CmsExtendedNotificationCause> m_resources; 072 073 /** 074 * Collects all resources that will expire in short time, or will become valid, or are not modified since a long time.<p> 075 * 076 * @param cms the CmsObject 077 * 078 * @throws CmsException if something goes wrong 079 */ 080 public CmsNotificationCandidates(CmsObject cms) 081 throws CmsException { 082 083 m_resources = new ArrayList<CmsExtendedNotificationCause>(); 084 m_cms = cms; 085 m_cms.getRequestContext().setCurrentProject( 086 m_cms.readProject(OpenCms.getSystemInfo().getNotificationProject())); 087 String folder = "/"; 088 GregorianCalendar now = new GregorianCalendar(TimeZone.getDefault(), CmsLocaleManager.getDefaultLocale()); 089 now.setTimeInMillis(System.currentTimeMillis()); 090 GregorianCalendar inOneWeek = (GregorianCalendar)now.clone(); 091 inOneWeek.add(Calendar.WEEK_OF_YEAR, 1); 092 Iterator<CmsResource> resources; 093 CmsResource resource; 094 095 // read all files with the 'notification-interval' property set 096 try { 097 resources = m_cms.readResourcesWithProperty( 098 folder, 099 CmsPropertyDefinition.PROPERTY_NOTIFICATION_INTERVAL).iterator(); 100 while (resources.hasNext()) { 101 resource = resources.next(); 102 int notification_interval = Integer.parseInt( 103 m_cms.readPropertyObject( 104 resource, 105 CmsPropertyDefinition.PROPERTY_NOTIFICATION_INTERVAL, 106 true).getValue()); 107 GregorianCalendar intervalBefore = new GregorianCalendar( 108 TimeZone.getDefault(), 109 CmsLocaleManager.getDefaultLocale()); 110 intervalBefore.setTimeInMillis(resource.getDateLastModified()); 111 intervalBefore.add(Calendar.DAY_OF_YEAR, notification_interval); 112 GregorianCalendar intervalAfter = (GregorianCalendar)intervalBefore.clone(); 113 intervalAfter.add(Calendar.WEEK_OF_YEAR, -1); 114 115 for (int i = 0; (i < 100) && intervalAfter.getTime().before(now.getTime()); i++) { 116 if (intervalBefore.getTime().after(now.getTime())) { 117 m_resources.add( 118 new CmsExtendedNotificationCause( 119 resource, 120 CmsExtendedNotificationCause.RESOURCE_UPDATE_REQUIRED, 121 intervalBefore.getTime())); 122 } 123 intervalBefore.add(Calendar.DAY_OF_YEAR, notification_interval); 124 intervalAfter.add(Calendar.DAY_OF_YEAR, notification_interval); 125 } 126 } 127 } catch (CmsDbEntryNotFoundException e) { 128 // no resources with property 'notification-interval', ignore 129 } 130 131 // read all files that were not modified longer than the max notification-time 132 GregorianCalendar oneYearAgo = (GregorianCalendar)now.clone(); 133 oneYearAgo.add(Calendar.DAY_OF_YEAR, -OpenCms.getSystemInfo().getNotificationTime()); 134 // create a resource filter to get the resources with 135 CmsResourceFilter filter = CmsResourceFilter.IGNORE_EXPIRATION.addRequireLastModifiedBefore( 136 oneYearAgo.getTimeInMillis()); 137 resources = m_cms.readResources(folder, filter).iterator(); 138 while (resources.hasNext()) { 139 resource = resources.next(); 140 m_resources.add( 141 new CmsExtendedNotificationCause( 142 resource, 143 CmsExtendedNotificationCause.RESOURCE_OUTDATED, 144 new Date(resource.getDateLastModified()))); 145 } 146 147 // get all resources that will expire within the next week 148 CmsResourceFilter resourceFilter = CmsResourceFilter.IGNORE_EXPIRATION.addRequireExpireBefore( 149 inOneWeek.getTimeInMillis()); 150 resourceFilter = resourceFilter.addRequireExpireAfter(now.getTimeInMillis()); 151 resources = m_cms.readResources(folder, resourceFilter).iterator(); 152 while (resources.hasNext()) { 153 resource = resources.next(); 154 m_resources.add( 155 new CmsExtendedNotificationCause( 156 resource, 157 CmsExtendedNotificationCause.RESOURCE_EXPIRES, 158 new Date(resource.getDateExpired()))); 159 } 160 161 // get all resources that will release within the next week 162 resourceFilter = CmsResourceFilter.IGNORE_EXPIRATION.addRequireReleaseBefore(inOneWeek.getTimeInMillis()); 163 resourceFilter = resourceFilter.addRequireReleaseAfter(now.getTimeInMillis()); 164 resources = m_cms.readResources(folder, resourceFilter).iterator(); 165 while (resources.hasNext()) { 166 resource = resources.next(); 167 m_resources.add( 168 new CmsExtendedNotificationCause( 169 resource, 170 CmsExtendedNotificationCause.RESOURCE_RELEASE, 171 new Date(resource.getDateReleased()))); 172 } 173 } 174 175 /** 176 * Sends all notifications to the responsible users.<p> 177 * 178 * @return a string listing all responsibles that a notification was sent to 179 * 180 * @throws CmsException if something goes wrong 181 */ 182 public String notifyResponsibles() throws CmsException { 183 184 Iterator<CmsContentNotification> notifications = filterConfirmedResources(getContentNotifications()).iterator(); 185 if (notifications.hasNext()) { 186 StringBuffer result = new StringBuffer( 187 Messages.get().getBundle().key(Messages.LOG_NOTIFICATIONS_SENT_TO_0)); 188 result.append(' '); 189 while (notifications.hasNext()) { 190 CmsContentNotification contentNotification = notifications.next(); 191 result.append(contentNotification.getResponsible().getName()); 192 if (notifications.hasNext()) { 193 result.append(", "); 194 } 195 try { 196 contentNotification.send(); 197 } catch (EmailException e) { 198 LOG.error(e.getLocalizedMessage(), e); 199 } 200 } 201 return result.toString(); 202 } else { 203 return Messages.get().getBundle().key(Messages.LOG_NO_NOTIFICATIONS_SENT_0); 204 } 205 } 206 207 /** 208 * Returns a collection of CmsContentNotifications, one for each responsible that receives a notification.<p> 209 * 210 * @return the list of CmsContentNotifications, one for each responsible that receives a notification 211 * 212 * @throws CmsException if something goes wrong 213 */ 214 protected Collection<CmsContentNotification> getContentNotifications() throws CmsException { 215 216 // get all owners for the resource 217 Iterator<CmsExtendedNotificationCause> notificationCandidates = m_resources.iterator(); 218 Map<CmsUser, CmsContentNotification> result = new HashMap<CmsUser, CmsContentNotification>(); 219 while (notificationCandidates.hasNext()) { 220 CmsExtendedNotificationCause resourceInfo = notificationCandidates.next(); 221 CmsResource resource = resourceInfo.getResource(); 222 // skip, if content notification is not enabled for this resource 223 String enableNotification = m_cms.readPropertyObject( 224 resource, 225 CmsPropertyDefinition.PROPERTY_ENABLE_NOTIFICATION, 226 true).getValue(); 227 if (Boolean.valueOf(enableNotification).booleanValue()) { 228 try { 229 Iterator<CmsUser> responsibles = m_cms.readResponsibleUsers(resource).iterator(); 230 while (responsibles.hasNext()) { 231 CmsUser responsible = responsibles.next(); 232 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(responsible.getEmail())) { 233 // check, if resultset already contains a content notification for the user 234 CmsContentNotification contentNotification = result.get(responsible); 235 236 // if not add a new content notification 237 if (contentNotification == null) { 238 contentNotification = new CmsContentNotification(responsible, m_cms); 239 result.put(responsible, contentNotification); 240 } 241 List<CmsExtendedNotificationCause> resourcesForResponsible = contentNotification.getNotificationCauses(); 242 if (resourcesForResponsible == null) { 243 resourcesForResponsible = new ArrayList<CmsExtendedNotificationCause>(); 244 contentNotification.setNotificationCauses(resourcesForResponsible); 245 } 246 resourcesForResponsible.add(resourceInfo); 247 } 248 } 249 } catch (CmsException e) { 250 if (LOG.isInfoEnabled()) { 251 LOG.error(e.getLocalizedMessage(), e); 252 } 253 } 254 } 255 } 256 return result.values(); 257 } 258 259 /** 260 * Updates the resources that were confirmed by the user. That means deletes the resources that need not a 261 * notification any more. 262 * removes all resources which do not occur in the candidate list.<p> 263 * 264 * @param contentNotifications the list of {@link CmsContentNotification} objects to remove from the set of confirmed resources 265 * @return a new CmsConfirmedResources Object which all the resource removed 266 */ 267 private Collection<CmsContentNotification> filterConfirmedResources( 268 Collection<CmsContentNotification> contentNotifications) { 269 270 Iterator<CmsContentNotification> notifications = contentNotifications.iterator(); 271 while (notifications.hasNext()) { 272 CmsContentNotification contentNotification = notifications.next(); 273 CmsUser responsible = contentNotification.getResponsible(); 274 // check, if user was already notified 275 List<?> confirmedResourcesList = (List<?>)responsible.getAdditionalInfo( 276 CmsUserSettings.ADDITIONAL_INFO_CONFIRMED_RESOURCES); 277 if (confirmedResourcesList == null) { 278 confirmedResourcesList = new ArrayList<Object>(); 279 responsible.setAdditionalInfo( 280 CmsUserSettings.ADDITIONAL_INFO_CONFIRMED_RESOURCES, 281 new ArrayList<Object>()); 282 } 283 284 List<CmsExtendedNotificationCause> notificationCandidates = contentNotification.getNotificationCauses(); 285 286 List<CmsExtendedNotificationCause> notificationResources = new ArrayList<CmsExtendedNotificationCause>( 287 notificationCandidates); 288 // remove already confirmed resources 289 Iterator<?> i = confirmedResourcesList.iterator(); 290 while (i.hasNext()) { 291 Object o = i.next(); 292 if (notificationResources.contains(o)) { 293 notificationResources.remove(o); 294 } 295 } 296 // filter confirmed resources 297 i = new ArrayList<Object>(confirmedResourcesList).iterator(); 298 while (i.hasNext()) { 299 Object o = i.next(); 300 if (!notificationCandidates.contains(o)) { 301 confirmedResourcesList.remove(o); 302 } 303 } 304 305 if (notificationResources.isEmpty()) { 306 // Remove notification, if resource list is empty 307 contentNotifications.remove(contentNotification); 308 } else { 309 contentNotification.setNotificationCauses(notificationResources); 310 } 311 try { 312 m_cms.writeUser(responsible); 313 } catch (CmsException e) { 314 LOG.error(e.getLocalizedMessage(), e); 315 } 316 } 317 return contentNotifications; 318 } 319}