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}