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