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.core.api.LifeCycleConstants.DELETED_STATE;
021import static org.nuxeo.ecm.core.api.LifeCycleConstants.DELETE_TRANSITION;
022import static org.nuxeo.ecm.core.api.LifeCycleConstants.TRANSTION_EVENT_OPTION_TRANSITION;
023import static org.nuxeo.ecm.core.api.LifeCycleConstants.UNDELETE_TRANSITION;
024import static org.nuxeo.ecm.core.api.event.DocumentEventTypes.ABOUT_TO_REMOVE;
025import static org.nuxeo.ecm.core.api.event.DocumentEventTypes.ABOUT_TO_REMOVE_VERSION;
026import static org.nuxeo.ecm.core.api.event.DocumentEventTypes.BEFORE_DOC_UPDATE;
027import static org.nuxeo.ecm.core.api.event.DocumentEventTypes.DOCUMENT_CHECKEDIN;
028import static org.nuxeo.ecm.core.api.event.DocumentEventTypes.DOCUMENT_CHECKEDOUT;
029import static org.nuxeo.ecm.core.api.event.DocumentEventTypes.DOCUMENT_CREATED;
030import static org.nuxeo.ecm.core.api.event.DocumentEventTypes.DOCUMENT_CREATED_BY_COPY;
031import static org.nuxeo.ecm.core.api.event.DocumentEventTypes.DOCUMENT_MOVED;
032import static org.nuxeo.ecm.quota.size.SizeUpdateEventContext.DOCUMENT_UPDATE_INITIAL_STATISTICS;
033
034import java.io.IOException;
035import java.io.Serializable;
036import java.util.ArrayList;
037import java.util.Collection;
038import java.util.HashSet;
039import java.util.List;
040import java.util.Map;
041import java.util.Set;
042
043import org.apache.commons.logging.Log;
044import org.apache.commons.logging.LogFactory;
045import org.nuxeo.common.collections.ScopeType;
046import org.nuxeo.ecm.core.api.Blob;
047import org.nuxeo.ecm.core.api.CoreSession;
048import org.nuxeo.ecm.core.api.DocumentModel;
049import org.nuxeo.ecm.core.api.DocumentRef;
050import org.nuxeo.ecm.core.api.IdRef;
051import org.nuxeo.ecm.core.api.IterableQueryResult;
052import org.nuxeo.ecm.core.api.event.CoreEventConstants;
053import org.nuxeo.ecm.core.api.model.Property;
054import org.nuxeo.ecm.core.event.Event;
055import org.nuxeo.ecm.core.event.EventService;
056import org.nuxeo.ecm.core.event.impl.DocumentEventContext;
057import org.nuxeo.ecm.core.utils.BlobsExtractor;
058import org.nuxeo.ecm.quota.AbstractQuotaStatsUpdater;
059import org.nuxeo.ecm.quota.QuotaStatsInitialWork;
060import org.nuxeo.ecm.quota.QuotaUtils;
061import org.nuxeo.runtime.api.Framework;
062
063/**
064 * {@link org.nuxeo.ecm.quota.QuotaStatsUpdater} counting space used by Blobs in document. This default implementation
065 * does not track the space used by versions, or the space used by non-Blob properties
066 *
067 * @author <a href="mailto:tdelprat@nuxeo.com">Tiry</a>
068 * @since 5.6
069 */
070public class QuotaSyncListenerChecker extends AbstractQuotaStatsUpdater {
071
072    public static final String DISABLE_QUOTA_CHECK_LISTENER = "disableQuotaListener";
073
074    private static Log log = LogFactory.getLog(QuotaSyncListenerChecker.class);
075
076    @Override
077    public void computeInitialStatistics(CoreSession unrestrictedSession, QuotaStatsInitialWork currentWorker) {
078        QuotaComputerProcessor processor = new QuotaComputerProcessor();
079        String query = "SELECT ecm:uuid FROM Document where ecm:isCheckedInVersion=0 and ecm:isProxy=0 order by dc:created desc";
080        IterableQueryResult res = unrestrictedSession.queryAndFetch(query, "NXQL");
081        log.debug("Starting initial Quota computation");
082        long total = res.size();
083        log.debug("Start iteration on " + total + " items");
084        try {
085            for (Map<String, Serializable> r : res) {
086                String uuid = (String) r.get("ecm:uuid");
087                // this will force an update if the plugin was installed and
088                // then removed
089                removeFacet(unrestrictedSession, uuid);
090            }
091        } finally {
092            res.close();
093        }
094        removeFacet(unrestrictedSession, unrestrictedSession.getRootDocument().getId());
095        unrestrictedSession.save();
096        try {
097            long idx = 0;
098            res = unrestrictedSession.queryAndFetch(query, "NXQL");
099            for (Map<String, Serializable> r : res) {
100                String uuid = (String) r.get("ecm:uuid");
101                computeSizeOnDocument(unrestrictedSession, uuid, processor);
102                currentWorker.notifyProgress(++idx, total);
103            }
104        } finally {
105            res.close();
106        }
107    }
108
109    private void removeFacet(CoreSession unrestrictedSession, String uuid) {
110        DocumentModel target = unrestrictedSession.getDocument(new IdRef(uuid));
111        if (target.hasFacet(QuotaAwareDocument.DOCUMENTS_SIZE_STATISTICS_FACET)) {
112            if (log.isTraceEnabled()) {
113                log.trace("doc with uuid " + uuid + " already up to date");
114            }
115            QuotaAware quotaDoc = target.getAdapter(QuotaAware.class);
116            quotaDoc.resetInfos(true);
117            if (log.isDebugEnabled()) {
118                log.debug(target.getPathAsString() + " reset to " + quotaDoc.getQuotaInfo());
119            }
120            target.removeFacet(QuotaAwareDocument.DOCUMENTS_SIZE_STATISTICS_FACET);
121            DocumentModel origTarget = target;
122            QuotaUtils.disableListeners(target);
123            target = unrestrictedSession.saveDocument(target);
124            QuotaUtils.clearContextData(target);
125            QuotaUtils.clearContextData(origTarget);
126        }
127    }
128
129    protected void computeSizeOnDocument(CoreSession unrestrictedSession, String uuid, QuotaComputerProcessor processor)
130            {
131        IdRef ref = new IdRef(uuid);
132        DocumentModel target = unrestrictedSession.getDocument(ref);
133        if (log.isTraceEnabled()) {
134            log.trace("process Quota initial computation on uuid " + uuid);
135        }
136        if (unrestrictedSession.exists(ref)) {
137            if (log.isTraceEnabled()) {
138                log.trace("doc with uuid " + uuid + " started update");
139            }
140            SizeUpdateEventContext quotaCtx = updateEventToProcessNewDocument(unrestrictedSession, target);
141            quotaCtx.setProperty(SizeUpdateEventContext.SOURCE_EVENT_PROPERTY_KEY, DOCUMENT_UPDATE_INITIAL_STATISTICS);
142            quotaCtx.getProperties().put(SizeUpdateEventContext._UPDATE_TRASH_SIZE,
143                    DELETED_STATE.equals(target.getCurrentLifeCycleState()));
144            processor.processQuotaComputation(quotaCtx);
145            if (log.isTraceEnabled()) {
146                log.trace("doc with uuid " + uuid + " update completed");
147            }
148        } else {
149            if (log.isTraceEnabled()) {
150                log.trace("doc with uuid " + uuid + " does not exist");
151            }
152        }
153    }
154
155    @Override
156    protected void handleQuotaExceeded(QuotaExceededException e, Event event) {
157        String msg = "Current event " + event.getName() + " would break Quota restriction, rolling back";
158        log.info(msg);
159        e.addInfo(msg);
160        event.markRollBack("Quota Exceeded", e);
161    }
162
163    @Override
164    protected void processDocumentCreated(CoreSession session, DocumentModel targetDoc, DocumentEventContext docCtx)
165            {
166
167        if (targetDoc.isVersion()) {
168            // version taken into account by checkout
169            // TODO 5.7 version accounting should be different
170            return;
171        }
172        BlobSizeInfo bsi = computeSizeImpact(targetDoc, false);
173
174        // always add the quota facet
175        QuotaAware quotaDoc = targetDoc.getAdapter(QuotaAware.class);
176        if (quotaDoc == null) {
177            log.trace("  add Quota Facet on " + targetDoc.getPathAsString());
178            QuotaAwareDocumentFactory.make(targetDoc, true);
179        }
180        // only process if blobs are present
181        if (bsi.getBlobSizeDelta() != 0) {
182            checkConstraints(session, targetDoc, targetDoc.getParentRef(), bsi);
183            SizeUpdateEventContext asyncEventCtx = new SizeUpdateEventContext(session, docCtx, bsi, DOCUMENT_CREATED);
184            sendUpdateEvents(asyncEventCtx);
185        }
186    }
187
188    @Override
189    protected void processDocumentCheckedIn(CoreSession session, DocumentModel doc, DocumentEventContext docCtx)
190            {
191        // on checkin the versions size is incremented (and also the total)
192
193        BlobSizeInfo bsi = computeSizeImpact(doc, false);
194        // only process if blobs are present
195        if (bsi.getBlobSize() != 0) {
196            // no checkConstraints as total size not impacted
197            SizeUpdateEventContext asyncEventCtx = new SizeUpdateEventContext(session, docCtx, bsi, DOCUMENT_CHECKEDIN);
198            sendUpdateEvents(asyncEventCtx);
199        }
200    }
201
202    @Override
203    protected void processDocumentCheckedOut(CoreSession session, DocumentModel doc, DocumentEventContext docCtx)
204            {
205        // on checkout we account in the total for the last version size
206        BlobSizeInfo bsi = computeSizeImpact(doc, false);
207        // only process if blobs are present
208        if (bsi.getBlobSize() != 0) {
209            checkConstraints(session, doc, doc.getParentRef(), bsi, true);
210            SizeUpdateEventContext asyncEventCtx = new SizeUpdateEventContext(session, docCtx, bsi, DOCUMENT_CHECKEDOUT);
211            sendUpdateEvents(asyncEventCtx);
212        }
213    }
214
215    @Override
216    protected void processDocumentUpdated(CoreSession session, DocumentModel doc, DocumentEventContext docCtx)
217            {
218        // Nothing to do !
219    }
220
221    @Override
222    protected void processDocumentBeforeUpdate(CoreSession session, DocumentModel targetDoc, DocumentEventContext docCtx)
223            {
224
225        BlobSizeInfo bsi = computeSizeImpact(targetDoc, true);
226        log.debug("calling processDocumentBeforeUpdate, bsi=" + bsi.toString());
227        // only process if Blobs where added or removed
228        if (bsi.getBlobSizeDelta() != 0) {
229            checkConstraints(session, targetDoc, targetDoc.getParentRef(), bsi);
230            SizeUpdateEventContext asyncEventCtx = new SizeUpdateEventContext(session, docCtx, bsi, BEFORE_DOC_UPDATE);
231            sendUpdateEvents(asyncEventCtx);
232        }
233    }
234
235    @Override
236    protected void processDocumentCopied(CoreSession session, DocumentModel targetDoc, DocumentEventContext docCtx)
237            {
238        QuotaAware quotaDoc = targetDoc.getAdapter(QuotaAware.class);
239        if (quotaDoc != null) {
240            long total = quotaDoc.getTotalSize() - quotaDoc.getVersionsSize() - quotaDoc.getTrashSize();
241            BlobSizeInfo bsi = new BlobSizeInfo();
242            bsi.blobSize = total;
243            bsi.blobSizeDelta = total;
244            if (total > 0) {
245                // check on parent since Session is not committed for now
246                checkConstraints(session, targetDoc, targetDoc.getParentRef(), bsi);
247                SizeUpdateEventContext asyncEventCtx = new SizeUpdateEventContext(session, docCtx, bsi,
248                        DOCUMENT_CREATED_BY_COPY);
249                sendUpdateEvents(asyncEventCtx);
250            }
251        }
252    }
253
254    @Override
255    protected void processDocumentMoved(CoreSession session, DocumentModel targetDoc, DocumentModel sourceParent,
256            DocumentEventContext docCtx) {
257
258        if (docCtx.getProperties().get(CoreEventConstants.DESTINATION_REF)
259                .equals(sourceParent.getRef())) {
260            log.debug(targetDoc.getPathAsString() + "(" + targetDoc.getId() + ") - document is just being renamed, skipping");
261            return;
262        }
263        QuotaAware quotaDoc = targetDoc.getAdapter(QuotaAware.class);
264        long total = 0;
265        if (quotaDoc != null) {
266            total = quotaDoc.getTotalSize();
267        }
268        BlobSizeInfo bsi = new BlobSizeInfo();
269        bsi.blobSize = total;
270        bsi.blobSizeDelta = total;
271        if (total > 0) {
272            // check on destination parent since Session is not committed for
273            // now
274            checkConstraints(session, targetDoc, targetDoc.getParentRef(), bsi);
275            SizeUpdateEventContext asyncEventCtx = new SizeUpdateEventContext(session, docCtx, bsi, DOCUMENT_MOVED);
276            long versSize = quotaDoc.getVersionsSize();
277            asyncEventCtx.setVersionsSize(versSize);
278            sendUpdateEvents(asyncEventCtx);
279
280            // also need to trigger update on source tree
281            BlobSizeInfo bsiRemove = new BlobSizeInfo();
282            bsiRemove.blobSize = total;
283            bsiRemove.blobSizeDelta = -total;
284
285            asyncEventCtx = new SizeUpdateEventContext(session, docCtx, sourceParent, bsiRemove, DOCUMENT_MOVED);
286            versSize = -quotaDoc.getVersionsSize();
287            asyncEventCtx.setVersionsSize(versSize);
288            List<String> sourceParentUUIDs = getParentUUIDS(session, sourceParent);
289            sourceParentUUIDs.add(0, sourceParent.getId());
290            asyncEventCtx.setParentUUIds(sourceParentUUIDs);
291            sendUpdateEvents(asyncEventCtx);
292        }
293
294    }
295
296    @Override
297    protected void processDocumentAboutToBeRemoved(CoreSession session, DocumentModel targetDoc,
298            DocumentEventContext docCtx) {
299
300        if (targetDoc.isVersion()) {
301            // for versions we need to decrement the live doc + it's parents
302            List<String> parentUUIDs = new ArrayList<String>();
303            parentUUIDs.add(targetDoc.getSourceId());
304            parentUUIDs.addAll(getParentUUIDS(session, new IdRef(targetDoc.getSourceId())));
305
306            // We only have to decrement the inner size of this doc
307            QuotaAware quotaDoc = targetDoc.getAdapter(QuotaAware.class);
308            // we do not write the right quota on the version, so we need to recompute it instead of quotaDoc#getInnerSize
309            BlobSizeInfo bsi = computeSizeImpact(targetDoc, false);
310            SizeUpdateEventContext asyncEventCtx = new SizeUpdateEventContext(session, docCtx, bsi.getBlobSize(),
311                    ABOUT_TO_REMOVE_VERSION);
312            asyncEventCtx.setParentUUIds(parentUUIDs);
313            sendUpdateEvents(asyncEventCtx);
314            return;
315        }
316
317        QuotaAware quotaDoc = targetDoc.getAdapter(QuotaAware.class);
318        long total = 0;
319        long versSize = 0;
320        if (quotaDoc == null) {
321            // the document could have been just created and the previous
322            // computation
323            // hasn't finished yet, see NXP-13665
324            BlobSizeInfo bsi = computeSizeImpact(targetDoc, false);
325            total = bsi.getBlobSize();
326            log.debug("Document " + targetDoc.getId() + " doesn't have the facet quotaDoc. Compute impacted size:"
327                    + total);
328        }
329        if (quotaDoc != null) {
330            total = quotaDoc.getTotalSize();
331            versSize = -quotaDoc.getVersionsSize();
332            log.debug("Found facet quotaDoc on document  " + targetDoc.getId()
333                    + ".Notifying QuotaComputerProcessor with total size: " + total + " and versions size: " + versSize);
334        }
335        if (total > 0) {
336            List<String> parentUUIDs = getParentUUIDS(session, targetDoc);
337            SizeUpdateEventContext asyncEventCtx = new SizeUpdateEventContext(session, docCtx, total, ABOUT_TO_REMOVE);
338            // remove size for all its versions from sizeVersions on parents
339            if (versSize != 0) {
340                asyncEventCtx.setVersionsSize(versSize);
341            }
342            asyncEventCtx.setParentUUIds(parentUUIDs);
343            asyncEventCtx.getProperties().put(SizeUpdateEventContext._UPDATE_TRASH_SIZE,
344                    DELETED_STATE.equals(targetDoc.getCurrentLifeCycleState()));
345            sendUpdateEvents(asyncEventCtx);
346        }
347    }
348
349    @Override
350    protected boolean needToProcessEventOnDocument(Event event, DocumentModel targetDoc) {
351
352        if (targetDoc == null) {
353            return false;
354        }
355        if (targetDoc.isProxy()) {
356            log.debug("Escape from listener: not precessing proxies");
357            return false;
358        }
359
360        Boolean block = (Boolean) targetDoc.getContextData(DISABLE_QUOTA_CHECK_LISTENER);
361        if (Boolean.TRUE.equals(block)) {
362            log.debug("Escape from listener to avoid reentrancy");
363            // ignore the event - we are blocked by the caller
364            // used to avoid reentrancy when the async event handler
365            // do update the docs to set the new size !
366            return false;
367        }
368        return true;
369    }
370
371    protected void sendUpdateEvents(SizeUpdateEventContext eventCtx) {
372
373        Event quotaUpdateEvent = eventCtx.newQuotaUpdateEvent();
374        log.debug("prepared event on target tree with context " + eventCtx.toString());
375        EventService es = Framework.getLocalService(EventService.class);
376        es.fireEvent(quotaUpdateEvent);
377    }
378
379    protected List<String> getParentUUIDS(CoreSession unrestrictedSession, final DocumentRef docRef)
380            {
381
382        final List<String> result = new ArrayList<String>();
383        if (docRef == null || docRef.toString() == null) {
384            return result;
385        }
386        DocumentRef[] parentRefs = unrestrictedSession.getParentDocumentRefs(docRef);
387        for (DocumentRef parentRef : parentRefs) {
388            result.add(parentRef.toString());
389        }
390        return result;
391    }
392
393    protected List<String> getParentUUIDS(CoreSession unrestrictedSession, final DocumentModel doc)
394            {
395        return getParentUUIDS(unrestrictedSession, doc.getRef());
396    }
397
398    protected void checkConstraints(CoreSession unrestrictedSession, final DocumentModel doc,
399            final DocumentRef parentRef, final BlobSizeInfo bsi) {
400        checkConstraints(unrestrictedSession, doc, parentRef, bsi, false);
401    }
402
403    protected void checkConstraints(CoreSession unrestrictedSession, final DocumentModel doc,
404            final DocumentRef parentRef, final BlobSizeInfo bsi, final boolean checkWithTotalSize)
405            {
406        if (parentRef == null) {
407            return;
408        }
409
410        long addition = bsi.blobSizeDelta;
411        if (checkWithTotalSize) {
412            addition = bsi.getBlobSize();
413        }
414
415        if (addition <= 0) {
416            return;
417        }
418        List<DocumentModel> parents = unrestrictedSession.getParentDocuments(parentRef);
419        DocumentModel parentDoc = unrestrictedSession.getDocument(parentRef);
420        if (!parents.contains(parentDoc)) {
421            parents.add(parentDoc);
422        }
423        for (DocumentModel parent : parents) {
424            log.debug("processing " + parent.getId() + " " + parent.getPathAsString());
425            QuotaAware qap = parent.getAdapter(QuotaAware.class);
426            // when enabling quota on user workspaces, the max size set on
427            // the
428            // UserWorkspacesRoot is the max size set on every user workspace
429            if (qap != null && !"UserWorkspacesRoot".equals(parent.getType()) && qap.getMaxQuota() > 0) {
430                Long newTotalSize = new Long(qap.getTotalSize() + addition);
431                try {
432                    if (qap.totalSizeCacheExists()) {
433                        Long oldTotalSize = qap.getTotalSizeCache();
434                        if (oldTotalSize == null) {
435                            newTotalSize = new Long(qap.getTotalSize() + addition);
436                            log.debug("to create cache entry to create: " + qap.getDoc().getId() + " total: " + newTotalSize);
437                        } else {
438                            newTotalSize = new Long(oldTotalSize + addition);
439                            log.debug("cache entry to update: " + qap.getDoc().getId() + " total: " + newTotalSize);
440                        }
441                        qap.putTotalSizeCache(newTotalSize);
442                        if (log.isDebugEnabled()) {
443                            log.debug("cache vs. DB: " + newTotalSize + " # " + (qap.getTotalSize() + addition));
444                        }
445                    }
446                } catch (IOException e) {
447                    log.error(e.getMessage() + ": unable to use cache " + QuotaAware.QUOTA_TOTALSIZE_CACHE_NAME + ", fallback to basic mechanism");
448                    newTotalSize = new Long(qap.getTotalSize() + addition);
449                }
450                if (newTotalSize > qap.getMaxQuota()) {
451                    log.info("Raising Quota Exception on " + doc.getPathAsString());
452                    try {
453                        qap.invalidateTotalSizeCache();
454                    } catch (IOException e) {
455                        log.error(e.getMessage() + ": unable to invalidate cache " + QuotaAware.QUOTA_TOTALSIZE_CACHE_NAME + " for " + qap.getDoc().getId());
456                    }
457                    throw new QuotaExceededException(parent, doc, qap.getMaxQuota());
458                }
459            }
460        }
461    }
462
463    protected BlobSizeInfo computeSizeImpact(DocumentModel doc, boolean onlyIfBlobHasChanged) {
464
465        BlobSizeInfo result = new BlobSizeInfo();
466
467        QuotaAware quotaDoc = doc.getAdapter(QuotaAware.class);
468        if (quotaDoc != null) {
469            result.blobSize = quotaDoc.getInnerSize();
470        } else {
471            result.blobSize = 0;
472        }
473
474        List<Blob> blobs = getBlobs(doc, onlyIfBlobHasChanged);
475
476        // If we have no blobs, it can mean
477        if (blobs.size() == 0) {
478            if (onlyIfBlobHasChanged) {
479                // Nothing has changed
480                result.blobSizeDelta = 0;
481            } else {
482                // Or the blob have been removed
483                result.blobSizeDelta = -result.blobSize;
484                result.blobSize = 0;
485            }
486        } else {
487            // When we have blobs
488            long size = 0;
489            for (Blob blob : blobs) {
490                if (blob != null) {
491                    size += blob.getLength();
492                }
493            }
494            result.blobSizeDelta = size - result.blobSize;
495            result.blobSize = size;
496        }
497
498        return result;
499    }
500
501    /**
502     * Return the list of changed blob
503     *
504     * @param doc
505     * @param onlyIfBlobHasChanged
506     * @return
507     */
508    protected List<Blob> getBlobs(DocumentModel doc, boolean onlyIfBlobHasChanged) {
509        QuotaSizeService sizeService = Framework.getService(QuotaSizeService.class);
510        Set<String> excludedPathSet = new HashSet<String>(sizeService.getExcludedPathList());
511
512        BlobsExtractor extractor = new BlobsExtractor();
513        extractor.setExtractorProperties(null, new HashSet<String>(excludedPathSet), true);
514
515        Collection<Property> blobProperties = extractor.getBlobsProperties(doc);
516
517        boolean needRecompute = !onlyIfBlobHasChanged;
518
519        if (onlyIfBlobHasChanged) {
520            for (Property blobProperty : blobProperties) {
521                if (blobProperty.isDirty()) {
522                    needRecompute = true;
523                    break;
524                }
525            }
526        }
527
528        List<Blob> result = new ArrayList<Blob>();
529        if (needRecompute) {
530            for (Property blobProperty : blobProperties) {
531                Blob blob = (Blob) blobProperty.getValue();
532                String schema = blobProperty.getParent().getSchema().getName();
533                String propName = blobProperty.getName();
534
535                log.debug(String.format("Using [%s:%s] for quota blob computation (size : %d)", schema, propName,
536                        blob.getLength()));
537                result.add(blob);
538            }
539        }
540        return result;
541    }
542
543    @Override
544    protected void processDocumentTrashOp(CoreSession session, DocumentModel doc, DocumentEventContext docCtx)
545            {
546        String transition = (String) docCtx.getProperties().get(TRANSTION_EVENT_OPTION_TRANSITION);
547        if (transition != null && (!(DELETE_TRANSITION.equals(transition) || UNDELETE_TRANSITION.equals(transition)))) {
548            return;
549        }
550
551        QuotaAware quotaDoc = doc.getAdapter(QuotaAware.class);
552        if (quotaDoc != null) {
553            long absSize = quotaDoc.getInnerSize();
554            if (log.isDebugEnabled()) {
555                if (quotaDoc.getDoc().isFolder()) {
556                    log.debug(quotaDoc.getDoc().getPathAsString() + " is a folder, just inner size (" + absSize + ") taken into account for trash size");
557                }
558            }
559            long total = (DELETE_TRANSITION.equals(transition) == true ? absSize : -absSize);
560            BlobSizeInfo bsi = new BlobSizeInfo();
561            bsi.blobSize = total;
562            bsi.blobSizeDelta = total;
563            if (absSize > 0) {
564                // check constrains not needed, since the documents stays in
565                // the same folder
566                // TODO move this check to QuotaSyncListenerChecker
567
568                SizeUpdateEventContext asyncEventCtx = new SizeUpdateEventContext(session, docCtx, bsi, transition);
569                sendUpdateEvents(asyncEventCtx);
570            }
571        }
572    }
573
574    @Override
575    protected void processDocumentBeforeRestore(CoreSession session, DocumentModel targetDoc,
576            DocumentEventContext docCtx) {
577        QuotaAware quotaDoc = targetDoc.getAdapter(QuotaAware.class);
578        if (quotaDoc != null) {
579            long total = quotaDoc.getTotalSize();
580            if (total > 0) {
581                List<String> parentUUIDs = getParentUUIDS(session, targetDoc);
582                SizeUpdateEventContext asyncEventCtx = new SizeUpdateEventContext(session, docCtx, total,
583                        ABOUT_TO_REMOVE);
584                // remove size for all its versions from sizeVersions on parents
585                // since they will be recalculated on restore
586                long versSize = -quotaDoc.getVersionsSize();
587                asyncEventCtx.setVersionsSize(versSize);
588                asyncEventCtx.setParentUUIds(parentUUIDs);
589                sendUpdateEvents(asyncEventCtx);
590            }
591        }
592    }
593
594    @Override
595    protected void processDocumentRestored(CoreSession session, DocumentModel targetDoc, DocumentEventContext docCtx)
596            {
597        QuotaAware quotaDoc = targetDoc.getAdapter(QuotaAware.class);
598        if (quotaDoc == null) {
599            log.debug("  add Quota Facet on " + targetDoc.getPathAsString());
600            quotaDoc = QuotaAwareDocumentFactory.make(targetDoc, true);
601        }
602        quotaDoc.resetInfos(true);
603        sendUpdateEvents(updateEventToProcessNewDocument(session, targetDoc));
604    }
605
606    private SizeUpdateEventContext updateEventToProcessNewDocument(CoreSession unrestrictedSession, DocumentModel target)
607            {
608        BlobSizeInfo bsi = computeSizeImpact(target, false);
609        SizeUpdateEventContext quotaCtx = null;
610
611        // process versions if any ; document is not in trash
612        List<DocumentModel> versions = unrestrictedSession.getVersions(target.getRef());
613        if (versions.isEmpty() && !DELETED_STATE.equals(target.getCurrentLifeCycleState())) {
614            quotaCtx = new SizeUpdateEventContext(unrestrictedSession, bsi, DOCUMENT_CREATED, target);
615
616        } else {
617            long versionsSize = 0;
618            for (DocumentModel documentModel : versions) {
619                versionsSize += computeSizeImpact(documentModel, false).blobSize;;
620            }
621
622            quotaCtx = new SizeUpdateEventContext(unrestrictedSession, bsi, DOCUMENT_UPDATE_INITIAL_STATISTICS, target);
623            quotaCtx.setVersionsSize(versionsSize);
624        }
625        return quotaCtx;
626    }
627}