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.scheduler.jobs; 029 030import org.opencms.db.CmsResourceState; 031import org.opencms.file.CmsObject; 032import org.opencms.file.CmsProject; 033import org.opencms.file.CmsProperty; 034import org.opencms.file.CmsPropertyDefinition; 035import org.opencms.file.CmsResource; 036import org.opencms.file.CmsResourceFilter; 037import org.opencms.file.types.I_CmsResourceType; 038import org.opencms.loader.CmsResourceManager; 039import org.opencms.lock.CmsLock; 040import org.opencms.main.CmsException; 041import org.opencms.main.OpenCms; 042import org.opencms.publish.CmsPublishManager; 043import org.opencms.report.CmsLogReport; 044import org.opencms.report.I_CmsReport; 045import org.opencms.scheduler.I_CmsScheduledJob; 046import org.opencms.util.CmsStringUtil; 047 048import java.util.Collections; 049import java.util.Iterator; 050import java.util.List; 051import java.util.Map; 052 053/** 054 * A schedulable OpenCms job to delete expired resources.<p> 055 * 056 * The user to execute the process should have have access to the required "Workplace manager" role.<p> 057 * 058 * The "Offline" project has to be configured for this job because the operations cannot be performed in the "Online" project.<p> 059 * 060 * Job parameters:<p> 061 * <dl> 062 * <dt><code>expirationdays={Number/Integer}</code></dt> 063 * <dd>Amount of days a resource has to be expired to be deleted.</dd> 064 * <dt><code>resourcetypes={csv list}</code></dt> 065 * <dd>Comma separated list of resource type names to specify the types of expired resources that may be deleted. 066 * If left out, expired resources of all types will be deleted. .</dd> 067 * <dt><code>folder={csv list}</code></dt> 068 * <dd>Allows to specify a comma separated list of folders in which all expired resources will be deleted. If omitted "/" will be taken as single folder 069 * for this operation. </dd> 070 * </dl> 071 * <p> 072 * 073 * The property "delete.expired" (<code>{@link CmsPropertyDefinition#PROPERTY_DELETE_EXPIRED}</code>) may be used 074 * to override the global setting of the parameter <code>expirationdays</code>. A value of "never", "false" or "none" will 075 * prevent resources from being deleted. Other values are "true" (default) or the amount of days a resource has 076 * to be expired for qualification of deletion.<p> 077 * 078 * Only published / unchanged files will be processed. Anything with unpublished changes will not 079 * be touched by the job. <p> 080 * 081 * Folders with expiration dates are ignored by default. Only if the scheduler parameter "resourcetypes" contains "folder" 082 * a folder that has been expired will be deleted (with all contained resources). <p> 083 * 084 * @since 7.5.0 085 */ 086public class CmsDeleteExpiredResourcesJob implements I_CmsScheduledJob { 087 088 /** Name of the parameter where to configure the amount of days a resource has to be expired before deletion. */ 089 public static final String PARAM_EXPIRATIONSDAYS = "expirationdays"; 090 091 /** Name of the parameter where to configure the resource types for resources to delete if expired. */ 092 public static final String PARAM_RESOURCETYPES = "resourcetypes"; 093 094 /** Name of the parameter where to configure the folder below which the operation will be done. */ 095 public static final String PARAM_FOLDER = "folder"; 096 097 /** Constant for calculation. */ 098 private static final long MILLIS_PER_DAY = 24 * 60 * 60 * 1000; 099 100 /** Setting for the <code>{@link CmsPropertyDefinition#PROPERTY_DELETE_EXPIRED}</code> to disallow deletion. */ 101 public static final String PROPERTY_VALUE_DELETE_EXPIRED_NEVER = "never"; 102 103 /** Setting for the <code>{@link CmsPropertyDefinition#PROPERTY_DELETE_EXPIRED}</code> to disallow deletion. */ 104 public static final String PROPERTY_VALUE_DELETE_EXPIRED_NONE = "none"; 105 106 /** 107 * @see org.opencms.scheduler.I_CmsScheduledJob#launch(org.opencms.file.CmsObject, java.util.Map) 108 */ 109 public String launch(CmsObject cms, Map<String, String> parameters) throws Exception { 110 111 // this job requires a higher runlevel than is allowed for all jobs: 112 if (OpenCms.getRunLevel() == OpenCms.RUNLEVEL_4_SERVLET_ACCESS) { 113 long currenttime = System.currentTimeMillis(); 114 115 // read the parameter for the versions to keep 116 int expirationdays = 30; 117 String expirationdaysparam = parameters.get(PARAM_EXPIRATIONSDAYS); 118 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(expirationdaysparam)) { 119 try { 120 expirationdays = Integer.parseInt(expirationdaysparam); 121 } catch (NumberFormatException nfe) { 122 // don't care 123 } 124 } 125 126 // read the parameter if to clear versions of deleted resources 127 String resTypes = parameters.get(PARAM_RESOURCETYPES); 128 String[] resTypesArr = null; 129 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(resTypes)) { 130 resTypesArr = CmsStringUtil.splitAsArray(resTypes, ','); 131 } 132 133 // read the optional parameter for the time range to keep versions 134 String[] topFoldersArr = new String[] {"/"}; 135 String topfolders = parameters.get(PARAM_FOLDER); 136 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(topfolders)) { 137 topFoldersArr = CmsStringUtil.splitAsArray(topfolders, ','); 138 } 139 140 // create a temp project for publishing everything together at the end: 141 CmsProject project = cms.createTempfileProject(); 142 cms.getRequestContext().setCurrentProject(project); 143 144 I_CmsReport report = new CmsLogReport( 145 cms.getRequestContext().getLocale(), 146 CmsDeleteExpiredResourcesJob.class); 147 report.println(Messages.get().container(Messages.RPT_DELETE_EXPIRED_START_0), I_CmsReport.FORMAT_HEADLINE); 148 149 // collect all resources: 150 List<CmsResource> resources = Collections.emptyList(); 151 CmsResourceFilter filter = CmsResourceFilter.ALL.addExcludeState(CmsResourceState.STATE_DELETED); 152 filter = filter.addRequireExpireBefore(currenttime); 153 154 // if we have configured resource types reading is more complicated because inclusion of several types 155 // is not supported by resource filter api: 156 int changedFiles = 0; 157 if (resTypesArr != null) { 158 I_CmsResourceType type; 159 CmsResourceManager resManager = OpenCms.getResourceManager(); 160 for (int i = resTypesArr.length - 1; i >= 0; i--) { 161 type = resManager.getResourceType(resTypesArr[i]); 162 filter = filter.addRequireType(type.getTypeId()); 163 for (int j = topFoldersArr.length - 1; j >= 0; j--) { 164 resources = cms.readResources(topFoldersArr[j], filter, true); 165 changedFiles += deleteExpiredResources(cms, report, resources, expirationdays, currenttime); 166 } 167 } 168 169 } else { 170 filter = filter.addRequireFile(); 171 for (int j = topFoldersArr.length - 1; j >= 0; j--) { 172 resources = cms.readResources(topFoldersArr[j], filter, true); 173 changedFiles += deleteExpiredResources(cms, report, resources, expirationdays, currenttime); 174 } 175 } 176 if (changedFiles > 0) { 177 CmsPublishManager publishManager = OpenCms.getPublishManager(); 178 publishManager.publishProject(cms, report); 179 // this is to not scramble the logging output: 180 publishManager.waitWhileRunning(); 181 } 182 report.println(Messages.get().container(Messages.RPT_DELETE_EXPIRED_END_0), I_CmsReport.FORMAT_HEADLINE); 183 } 184 return null; 185 } 186 187 /** 188 * Deletes the expired resources if the have been expired longer than the given amount of days. <p> 189 * 190 * At this level the resource type is not checked again. <p> 191 * 192 * @param resources a <code>List</code> containing <code>CmsResource</code> instances to process. 193 * @param cms needed to delete resources 194 * @param report needed to print messages to 195 * @param expirationdays the amount of days a resource has to be expired before it is deleted 196 * @param currenttime the current time in milliseconds since January 1st 1970 197 * @return the amount of deleted files 198 * 199 */ 200 private int deleteExpiredResources( 201 final CmsObject cms, 202 final I_CmsReport report, 203 final List<CmsResource> resources, 204 final int expirationdays, 205 final long currenttime) { 206 207 int result = 0; 208 CmsResource resource; 209 CmsLock lock; 210 CmsProperty property; 211 String propertyValue; 212 long expirationdate; 213 int expirationDaysPropertyOverride; 214 Iterator<CmsResource> it = resources.iterator(); 215 String resourcePath; 216 while (it.hasNext()) { 217 resource = it.next(); 218 resourcePath = cms.getRequestContext().removeSiteRoot(resource.getRootPath()); 219 report.print( 220 Messages.get().container(Messages.RPT_DELETE_EXPIRED_PROCESSING_1, new String[] {resourcePath}), 221 I_CmsReport.FORMAT_DEFAULT); 222 report.print(org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_DOTS_0)); 223 224 if (resource.getState() == CmsResourceState.STATE_UNCHANGED) { 225 expirationdate = resource.getDateExpired(); 226 expirationDaysPropertyOverride = expirationdays; 227 try { 228 property = cms.readPropertyObject(resource, CmsPropertyDefinition.PROPERTY_DELETE_EXPIRED, true); 229 propertyValue = property.getValue(); 230 if (!property.isNullProperty()) { 231 if (PROPERTY_VALUE_DELETE_EXPIRED_NEVER.equals(propertyValue) 232 || PROPERTY_VALUE_DELETE_EXPIRED_NONE.equals(propertyValue) 233 || Boolean.FALSE.toString().equals(propertyValue)) { 234 report.println( 235 Messages.get().container(Messages.RPT_DELETE_EXPIRED_PROPERTY_NEVER_0), 236 I_CmsReport.FORMAT_NOTE); 237 continue; 238 } else { 239 // true is allowed, but any other value will be treated as a configuration error and skip the 240 // resource: 241 242 if (!Boolean.TRUE.toString().equals(propertyValue)) { 243 // NumberFormatException should skip the resource because the property value was mistyped 244 expirationDaysPropertyOverride = Integer.parseInt(propertyValue); 245 } 246 } 247 } 248 249 // no Calendar - semantics required for simple timespan check: 250 if ((expirationdate != Long.MAX_VALUE) 251 && ((currenttime - expirationdate) > (expirationDaysPropertyOverride * MILLIS_PER_DAY))) { 252 lock = cms.getLock(resource); 253 if (lock.isNullLock()) { 254 cms.lockResource(resourcePath); 255 } else { 256 if (!lock.getUserId().equals(cms.getRequestContext().getCurrentUser().getId())) { 257 report.println( 258 Messages.get().container(Messages.RPT_DELETE_EXPIRED_LOCKED_0), 259 I_CmsReport.FORMAT_WARNING); 260 continue; 261 } 262 } 263 cms.deleteResource(resourcePath, CmsResource.DELETE_PRESERVE_SIBLINGS); 264 result++; 265 report.println( 266 org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_OK_0), 267 I_CmsReport.FORMAT_OK); 268 269 } else { 270 report.println( 271 Messages.get().container( 272 Messages.RPT_DELETE_EXPIRED_NOT_EXPIRED_1, 273 new Integer[] {Integer.valueOf(expirationDaysPropertyOverride)})); 274 } 275 } catch (Exception e) { 276 report.println( 277 Messages.get().container( 278 Messages.RPT_DELETE_EXPIRED_FAILED_1, 279 new String[] {CmsException.getStackTraceAsString(e)}), 280 I_CmsReport.FORMAT_ERROR); 281 282 } 283 } else { 284 report.println(Messages.get().container(Messages.RPT_DELETE_EXPIRED_UNPUBLISHED_0)); 285 } 286 } 287 return result; 288 } 289}