001/* 002 * Copyright 2011 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.cms.workflow.purge; 017 018import java.util.ArrayList; 019import java.util.LinkedHashSet; 020import java.util.List; 021import java.util.ListIterator; 022import java.util.Set; 023 024import javax.jcr.Node; 025import javax.jcr.RepositoryException; 026import javax.jcr.nodetype.NodeType; 027import javax.jcr.version.Version; 028import javax.jcr.version.VersionHistory; 029import javax.jcr.version.VersionIterator; 030import javax.jcr.version.VersionManager; 031 032import org.apache.avalon.framework.component.Component; 033import org.apache.avalon.framework.logger.AbstractLogEnabled; 034import org.apache.avalon.framework.service.ServiceException; 035import org.apache.avalon.framework.service.ServiceManager; 036import org.apache.avalon.framework.service.Serviceable; 037import org.joda.time.DateTime; 038 039import org.ametys.cms.repository.WorkflowAwareContent; 040import org.ametys.cms.repository.WorkflowAwareContentHelper; 041import org.ametys.plugins.repository.AmetysRepositoryException; 042import org.ametys.plugins.repository.RepositoryConstants; 043import org.ametys.runtime.config.Config; 044 045/** 046 * Component which purges content old versions. 047 */ 048public class PurgeVersionsManager extends AbstractLogEnabled implements Component, Serviceable 049{ 050 051 /** The avalon component role. */ 052 public static final String ROLE = PurgeVersionsManager.class.getName(); 053 054 /** The current step ID property. */ 055 private static final String __CURRENT_STEP_ID_PROPERTY = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + WorkflowAwareContentHelper.METADATA_CURRENT_STEP_ID; 056 057 @Override 058 public void service(ServiceManager manager) throws ServiceException 059 { 060 // Ignore. 061 } 062 063 /** 064 * Purge a content. 065 * @param content the content to purge. 066 * @param validationStepId the ID of the validation step for this content. 067 * @param firstVersionsToKeep the count of first versions to keep. 068 * @return the count of removed versions. 069 */ 070 public int purgeContent(WorkflowAwareContent content, long validationStepId, int firstVersionsToKeep) 071 { 072 try 073 { 074 Node node = content.getNode(); 075 076 if (node.isNodeType(NodeType.MIX_VERSIONABLE)) 077 { 078 // Retrieve the version history and base version of the content. 079 VersionManager versionManager = node.getSession().getWorkspace().getVersionManager(); 080 VersionHistory versionHistory = versionManager.getVersionHistory(node.getPath()); 081 Version baseVersion = versionManager.getBaseVersion(node.getPath()); 082 083 return purgeContent(content, versionHistory, baseVersion, validationStepId, firstVersionsToKeep); 084 } 085 086 return 0; 087 } 088 catch (RepositoryException e) 089 { 090 throw new AmetysRepositoryException("Error purging the content " + content.getId(), e); 091 } 092 } 093 094 /** 095 * Purge a content version history. 096 * @param content the content. 097 * @param versionHistory the version history to purge. 098 * @param baseVersion the content base version, must be kept. 099 * @param validationStepId the ID of the validation step for this content. 100 * @param firstVersionsToKeep the count of first versions to keep. 101 * @return the count of removed versions. 102 * @throws RepositoryException if an error occurs. 103 */ 104 protected int purgeContent(WorkflowAwareContent content, VersionHistory versionHistory, Version baseVersion, long validationStepId, int firstVersionsToKeep) throws RepositoryException 105 { 106 int validatedVersionsToKeep = Config.getInstance().getValueAsLong("purge.keep.validated.versions").intValue(); 107 int daysToKeep = Config.getInstance().getValueAsLong("purge.before.days").intValue(); 108 109 Set<String> versionsToRemove = new LinkedHashSet<>(); 110 111 DateTime limit = new DateTime().minusDays(daysToKeep); 112 113 // Get all the versions. 114 List<VersionInfo> allVersions = getAllVersions(versionHistory); 115 116 ListIterator<VersionInfo> versionIt = allVersions.listIterator(allVersions.size()); 117 118 int validatedVersions = 0; 119 try 120 { 121 if (content.getCurrentStepId() == validationStepId) 122 { 123 validatedVersions++; 124 } 125 } 126 catch (AmetysRepositoryException e) 127 { 128 validatedVersions = 0; 129 } 130 131 while (versionIt.hasPrevious()) 132 { 133 VersionInfo info = versionIt.previous(); 134 135 if (validatedVersions >= validatedVersionsToKeep && info.getCreated().isBefore(limit) && versionIt.previousIndex() >= firstVersionsToKeep - 1) 136 { 137 // Do not remove the base version. 138 if (!info.getName().equals(baseVersion.getName())) 139 { 140 versionsToRemove.add(info.getName()); 141 } 142 } 143 144 if (info.getStepId() != null && info.getStepId() == validationStepId) 145 { 146 validatedVersions++; 147 } 148 } 149 150 for (String version : versionsToRemove) 151 { 152 versionHistory.removeVersion(version); 153 } 154 155 return versionsToRemove.size(); 156 } 157 158 /** 159 * Get all versions of a version history. 160 * @param versionHistory the version history to purge. 161 * @return all the version infos. 162 * @throws RepositoryException if an error occurs. 163 */ 164 protected List<VersionInfo> getAllVersions(VersionHistory versionHistory) throws RepositoryException 165 { 166 List<VersionInfo> allVersions = new ArrayList<>(); 167 168 VersionIterator versions = versionHistory.getAllVersions(); 169 170 while (versions.hasNext()) 171 { 172 Version version = versions.nextVersion(); 173 174 Node node = version.getFrozenNode(); 175 176 VersionInfo info = new VersionInfo(); 177 178 info.setName(version.getName()); 179 info.setCreated(version.getCreated().getTimeInMillis()); 180 181 if (node.hasProperty(__CURRENT_STEP_ID_PROPERTY)) 182 { 183 long stepId = node.getProperty(__CURRENT_STEP_ID_PROPERTY).getLong(); 184 info.setStepId(stepId); 185 } 186 187 allVersions.add(info); 188 } 189 190 return allVersions; 191 } 192 193 /** 194 * Version information. 195 */ 196 protected class VersionInfo 197 { 198 199 /** The version name. */ 200 protected String _name; 201 202 /** The version creation date. */ 203 protected DateTime _created; 204 205 /** The version step ID. */ 206 protected Long _stepId; 207 208 /** 209 * Get the name. 210 * @return the name 211 */ 212 public String getName() 213 { 214 return _name; 215 } 216 217 /** 218 * Set the name. 219 * @param name the name to set 220 */ 221 public void setName(String name) 222 { 223 this._name = name; 224 } 225 226 /** 227 * Get the created. 228 * @return the created 229 */ 230 public DateTime getCreated() 231 { 232 return _created; 233 } 234 235 /** 236 * Set the created. 237 * @param created the created to set 238 */ 239 public void setCreated(long created) 240 { 241 this._created = new DateTime(created); 242 } 243 244 /** 245 * Get the stepId. 246 * @return the stepId 247 */ 248 public Long getStepId() 249 { 250 return _stepId; 251 } 252 253 /** 254 * Set the stepId. 255 * @param stepId the stepId to set 256 */ 257 public void setStepId(Long stepId) 258 { 259 this._stepId = stepId; 260 } 261 262 @Override 263 public String toString() 264 { 265 return _name; 266 } 267 268 } 269 270}