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