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}