001/* 002 * (C) Copyright 2006-2012 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl-2.1.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * <a href="mailto:tdelprat@nuxeo.com">Tiry</a> 016 */ 017 018package org.nuxeo.ecm.quota.size; 019 020import static org.nuxeo.ecm.platform.dublincore.listener.DublinCoreListener.DISABLE_DUBLINCORE_LISTENER; 021import static org.nuxeo.ecm.platform.ec.notification.NotificationConstants.DISABLE_NOTIFICATION_SERVICE; 022 023import java.io.IOException; 024 025import org.apache.commons.lang.StringUtils; 026import org.apache.commons.logging.Log; 027import org.apache.commons.logging.LogFactory; 028import org.nuxeo.common.collections.ScopeType; 029import org.nuxeo.ecm.core.api.DocumentModel; 030import org.nuxeo.ecm.core.api.PropertyException; 031import org.nuxeo.ecm.core.api.model.DeltaLong; 032import org.nuxeo.ecm.core.cache.Cache; 033import org.nuxeo.ecm.core.cache.CacheService; 034import org.nuxeo.ecm.quota.QuotaStatsService; 035import org.nuxeo.ecm.quota.QuotaUtils; 036import org.nuxeo.runtime.api.Framework; 037 038/** 039 * Adapter to manage a DocumentModel that supports Quotas 040 * 041 * @author <a href="mailto:tdelprat@nuxeo.com">Tiry</a> 042 * @since 5.6 043 */ 044public class QuotaAwareDocument implements QuotaAware { 045 046 public static final String DOCUMENTS_SIZE_STATISTICS_FACET = "DocumentsSizeStatistics"; 047 048 public static final String DOCUMENTS_SIZE_INNER_SIZE_PROPERTY = "dss:innerSize"; 049 050 public static final String DOCUMENTS_SIZE_TOTAL_SIZE_PROPERTY = "dss:totalSize"; 051 052 public static final String DOCUMENTS_SIZE_TRASH_SIZE_PROPERTY = "dss:sizeTrash"; 053 054 public static final String DOCUMENTS_SIZE_VERSIONS_SIZE_PROPERTY = "dss:sizeVersions"; 055 056 public static final String DOCUMENTS_SIZE_MAX_SIZE_PROPERTY = "dss:maxSize"; 057 058 protected DocumentModel doc; 059 060 protected static final Log log = LogFactory.getLog(QuotaAwareDocument.class); 061 062 public QuotaAwareDocument(DocumentModel doc) { 063 this.doc = doc; 064 } 065 066 @Override 067 public DocumentModel getDoc() { 068 return doc; 069 } 070 071 @Override 072 public long getInnerSize() { 073 try { 074 Long inner = (Long) doc.getPropertyValue(DOCUMENTS_SIZE_INNER_SIZE_PROPERTY); 075 return inner != null ? inner : 0; 076 } catch (PropertyException e) { 077 return 0; 078 } 079 } 080 081 @Override 082 public long getTotalSize() { 083 try { 084 Long total = (Long) doc.getPropertyValue(DOCUMENTS_SIZE_TOTAL_SIZE_PROPERTY); 085 return total != null ? total : 0; 086 } catch (PropertyException e) { 087 return 0; 088 } 089 } 090 091 @Override 092 public long getTrashSize() { 093 try { 094 Long total = (Long) doc.getPropertyValue(DOCUMENTS_SIZE_TRASH_SIZE_PROPERTY); 095 return total != null ? total : 0; 096 } catch (PropertyException e) { 097 return 0; 098 } 099 } 100 101 @Override 102 public long getVersionsSize() { 103 try { 104 Long total = (Long) doc.getPropertyValue(DOCUMENTS_SIZE_VERSIONS_SIZE_PROPERTY); 105 return total != null ? total : 0; 106 } catch (PropertyException e) { 107 return 0; 108 } 109 } 110 111 @Override 112 public void setInnerSize(long size, boolean save) { 113 doc.setPropertyValue(DOCUMENTS_SIZE_INNER_SIZE_PROPERTY, size); 114 doc.setPropertyValue(DOCUMENTS_SIZE_TOTAL_SIZE_PROPERTY, size); 115 if (log.isDebugEnabled()) { 116 log.debug("Setting quota (inner size) : " + size + " on document " + doc.getId()); 117 } 118 if (save) { 119 save(true); 120 } 121 } 122 123 protected Number addDelta(String property, long delta) { 124 Number oldValue = (Number) doc.getPropertyValue(property); 125 Number newValue = DeltaLong.deltaOrLong(oldValue, delta); 126 if (newValue.longValue() < 0L) { 127 newValue = 0L; 128 } 129 doc.setPropertyValue(property, newValue.longValue()); 130 return newValue; 131 } 132 133 @Override 134 public void addInnerSize(long additionalSize, boolean save) { 135 Number inner = addDelta(DOCUMENTS_SIZE_INNER_SIZE_PROPERTY, additionalSize); 136 Number total = addDelta(DOCUMENTS_SIZE_TOTAL_SIZE_PROPERTY, additionalSize); 137 if (log.isDebugEnabled()) { 138 log.debug("Setting quota (inner size) : " + inner + ", (total size) : " + total + " on document " 139 + doc.getId()); 140 } 141 if (save) { 142 save(true); 143 } 144 } 145 146 @Override 147 public void addTotalSize(long additionalSize, boolean save) { 148 Number total = addDelta(DOCUMENTS_SIZE_TOTAL_SIZE_PROPERTY, additionalSize); 149 if (log.isDebugEnabled()) { 150 log.debug("Setting quota (total size) : " + total + " on document " + doc.getId()); 151 } 152 if (save) { 153 save(true); 154 } 155 } 156 157 @Override 158 public void addTrashSize(long additionalSize, boolean save) { 159 Number trash = addDelta(DOCUMENTS_SIZE_TRASH_SIZE_PROPERTY, additionalSize); 160 if (log.isDebugEnabled()) { 161 log.debug("Setting quota (trash size):" + trash + " on document " + doc.getId()); 162 } 163 if (save) { 164 save(true); 165 } 166 } 167 168 @Override 169 public void addVersionsSize(long additionalSize, boolean save) { 170 Number versions = addDelta(DOCUMENTS_SIZE_VERSIONS_SIZE_PROPERTY, additionalSize); 171 if (log.isDebugEnabled()) { 172 log.debug("Setting quota (versions size): " + versions + " on document " + doc.getId()); 173 } 174 if (save) { 175 save(true); 176 } 177 } 178 179 @Override 180 public void save() { 181 doc.putContextData(QuotaSyncListenerChecker.DISABLE_QUOTA_CHECK_LISTENER, Boolean.TRUE); 182 QuotaUtils.disableListeners(doc); 183 DocumentModel origDoc = doc; 184 doc = doc.getCoreSession().saveDocument(doc); 185 QuotaUtils.clearContextData(doc); 186 QuotaUtils.clearContextData(origDoc); 187 } 188 189 @Override 190 public void save(boolean disableNotifications) { 191 // disableNotifications ignored 192 save(); 193 } 194 195 @Override 196 public long getMaxQuota() { 197 try { 198 Long count = (Long) doc.getPropertyValue(DOCUMENTS_SIZE_MAX_SIZE_PROPERTY); 199 return count != null ? count : -1; 200 } catch (PropertyException e) { 201 return -1; 202 } 203 } 204 205 @Override 206 public void setMaxQuota(long maxSize, boolean save, boolean skipValidation) { 207 if (!skipValidation) { 208 if (!(Framework.getLocalService(QuotaStatsService.class).canSetMaxQuota(maxSize, doc, doc.getCoreSession()))) { 209 throw new QuotaExceededException(doc, "Can not set " + maxSize 210 + ". Quota exceeded because the quota set on one of the children."); 211 } 212 } 213 doc.setPropertyValue(DOCUMENTS_SIZE_MAX_SIZE_PROPERTY, maxSize); 214 if (save) { 215 save(false); 216 } 217 } 218 219 @Override 220 public void setMaxQuota(long maxSize, boolean save) { 221 setMaxQuota(maxSize, save, false); 222 } 223 224 @Override 225 public QuotaInfo getQuotaInfo() { 226 return new QuotaInfo(getInnerSize(), getTotalSize(), getTrashSize(), getVersionsSize(), getMaxQuota()); 227 } 228 229 /** 230 * @since 5.7 231 */ 232 @Override 233 public void resetInfos(boolean save) { 234 doc.setPropertyValue(DOCUMENTS_SIZE_INNER_SIZE_PROPERTY, 0L); 235 doc.setPropertyValue(DOCUMENTS_SIZE_TOTAL_SIZE_PROPERTY, 0L); 236 doc.setPropertyValue(DOCUMENTS_SIZE_MAX_SIZE_PROPERTY, 0L); 237 doc.setPropertyValue(DOCUMENTS_SIZE_TRASH_SIZE_PROPERTY, 0L); 238 doc.setPropertyValue(DOCUMENTS_SIZE_VERSIONS_SIZE_PROPERTY, 0L); 239 try { 240 invalidateTotalSizeCache(); 241 } catch (IOException e) { 242 log.warn("Unable to invalidate cache"); 243 } 244 if (save) { 245 save(true); 246 } 247 } 248 249 @Override 250 public void invalidateTotalSizeCache() throws IOException { 251 invalidateCache(QUOTA_TOTALSIZE_CACHE_NAME); 252 } 253 254 protected Cache getCache(String cacheName) { 255 CacheService cs = Framework.getService(CacheService.class); 256 Cache cache = cs.getCache(cacheName); 257 if (cache != null) { 258 log.trace("Using cache " + cacheName); 259 return cache; 260 } else { 261 throw new RuntimeException("Unable to retrieve cache " + cacheName); 262 } 263 } 264 265 protected void invalidateCache(String cacheName) throws IOException { 266 try { 267 Cache cache = getCache(cacheName); 268 cache.invalidate(getCacheEntry(doc.getId())); 269 } catch (RuntimeException e) { 270 log.warn(e.getMessage()); 271 } 272 273 } 274 275 @Override 276 public Long getTotalSizeCache() throws IOException { 277 return getSizeInCache(QUOTA_TOTALSIZE_CACHE_NAME); 278 } 279 280 protected Long getSizeInCache(String cacheName) throws IOException { 281 try { 282 Cache cache = getCache(cacheName); 283 return (Long) cache.get(getCacheEntry(doc.getId())); 284 } catch (RuntimeException e) { 285 log.warn(e.getMessage()); 286 } 287 return null; 288 } 289 290 @Override 291 public void putTotalSizeCache(long size) throws IOException { 292 putSizeInCache(QUOTA_TOTALSIZE_CACHE_NAME, size); 293 } 294 295 protected void putSizeInCache(String cacheName, long size) throws IOException { 296 try { 297 Cache cache = getCache(cacheName); 298 cache.put(getCacheEntry(doc.getId()), size); 299 } catch (RuntimeException e) { 300 log.warn(e.getMessage()); 301 } 302 } 303 304 @Override 305 public boolean totalSizeCacheExists() { 306 try { 307 return (getCache(QUOTA_TOTALSIZE_CACHE_NAME) != null); 308 } catch (RuntimeException e) { 309 return false; 310 } 311 312 } 313 314 protected String getCacheEntry(String... params) { 315 return StringUtils.join(params, '-'); 316 } 317 318}