org.nuxeo.ecm.quota.count.DocumentsCountUpdater.java Source code

Java tutorial

Introduction

Here is the source code for org.nuxeo.ecm.quota.count.DocumentsCountUpdater.java

Source

/*
 * (C) Copyright 2006-2016 Nuxeo SA (http://nuxeo.com/) and others.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Contributors:
 *     Thomas Roger
 *     Florent Guillaume
 */
package org.nuxeo.ecm.quota.count;

import static org.nuxeo.ecm.core.schema.FacetNames.FOLDERISH;
import static org.nuxeo.ecm.quota.count.Constants.DOCUMENTS_COUNT_STATISTICS_CHILDREN_COUNT_PROPERTY;
import static org.nuxeo.ecm.quota.count.Constants.DOCUMENTS_COUNT_STATISTICS_DESCENDANTS_COUNT_PROPERTY;
import static org.nuxeo.ecm.quota.count.Constants.DOCUMENTS_COUNT_STATISTICS_FACET;

import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.nuxeo.ecm.core.api.CoreSession;
import org.nuxeo.ecm.core.api.DocumentModel;
import org.nuxeo.ecm.core.api.DocumentNotFoundException;
import org.nuxeo.ecm.core.api.IdRef;
import org.nuxeo.ecm.core.api.IterableQueryResult;
import org.nuxeo.ecm.core.api.model.DeltaLong;
import org.nuxeo.ecm.core.event.Event;
import org.nuxeo.ecm.quota.AbstractQuotaStatsUpdater;
import org.nuxeo.ecm.quota.QuotaStatsInitialWork;
import org.nuxeo.ecm.quota.QuotaUtils;
import org.nuxeo.ecm.quota.size.QuotaExceededException;
import org.nuxeo.runtime.transaction.TransactionHelper;

/**
 * {@link org.nuxeo.ecm.quota.QuotaStatsUpdater} counting the non folderish documents.
 * <p>
 * Store the descendant and children count on {@code Folderish} documents.
 *
 * @since 5.5
 */
public class DocumentsCountUpdater extends AbstractQuotaStatsUpdater {

    private static final Log log = LogFactory.getLog(DocumentsCountUpdater.class);

    public static final int BATCH_SIZE = 50;

    @Override
    protected void processDocumentCreated(CoreSession session, DocumentModel doc) {
        if (doc.isVersion()) {
            return;
        }
        List<DocumentModel> ancestors = getAncestors(session, doc);
        long docCount = getCount(doc);
        updateCountStatistics(session, doc, ancestors, docCount);
    }

    @Override
    protected void processDocumentCopied(CoreSession session, DocumentModel doc) {
        List<DocumentModel> ancestors = getAncestors(session, doc);
        long docCount = getCount(doc);
        updateCountStatistics(session, doc, ancestors, docCount);
    }

    @Override
    protected void processDocumentCheckedIn(CoreSession session, DocumentModel doc) {
        // NOP
    }

    @Override
    protected void processDocumentCheckedOut(CoreSession session, DocumentModel doc) {
        // NOP
    }

    @Override
    protected void processDocumentUpdated(CoreSession session, DocumentModel doc) {
    }

    @Override
    protected void processDocumentMoved(CoreSession session, DocumentModel doc, DocumentModel sourceParent) {
        List<DocumentModel> ancestors = getAncestors(session, doc);
        List<DocumentModel> sourceAncestors = getAncestors(session, sourceParent);
        sourceAncestors.add(0, sourceParent);
        long docCount = getCount(doc);
        updateCountStatistics(session, doc, ancestors, docCount);
        updateCountStatistics(session, doc, sourceAncestors, -docCount);
    }

    @Override
    protected void processDocumentAboutToBeRemoved(CoreSession session, DocumentModel doc) {
        List<DocumentModel> ancestors = getAncestors(session, doc);
        long docCount = getCount(doc);
        updateCountStatistics(session, doc, ancestors, -docCount);
    }

    @Override
    protected void handleQuotaExceeded(QuotaExceededException e, Event event) {
        // never rollback on Exceptions
    }

    @Override
    protected boolean needToProcessEventOnDocument(Event event, DocumentModel doc) {
        return true;
    }

    @Override
    protected void processDocumentBeforeUpdate(CoreSession session, DocumentModel doc) {
        // NOP
    }

    protected void updateCountStatistics(CoreSession session, DocumentModel doc, List<DocumentModel> ancestors,
            long count) {
        if (ancestors == null || ancestors.isEmpty()) {
            return;
        }
        if (count == 0) {
            return;
        }

        if (!doc.hasFacet(FOLDERISH)) {
            DocumentModel parent = ancestors.get(0);
            updateParentChildrenCount(session, parent, count);
        }

        for (DocumentModel ancestor : ancestors) {
            Number previous;
            if (ancestor.hasFacet(DOCUMENTS_COUNT_STATISTICS_FACET)) {
                previous = (Number) ancestor
                        .getPropertyValue(DOCUMENTS_COUNT_STATISTICS_DESCENDANTS_COUNT_PROPERTY);
            } else {
                ancestor.addFacet(DOCUMENTS_COUNT_STATISTICS_FACET);
                previous = null;
            }
            DeltaLong descendantsCount = DeltaLong.valueOf(previous, count);
            ancestor.setPropertyValue(DOCUMENTS_COUNT_STATISTICS_DESCENDANTS_COUNT_PROPERTY, descendantsCount);
            // do not send notifications
            QuotaUtils.disableListeners(ancestor);
            DocumentModel origAncestor = ancestor;
            session.saveDocument(ancestor);
            QuotaUtils.clearContextData(origAncestor);
        }

        session.save();
    }

    protected void updateParentChildrenCount(CoreSession session, DocumentModel parent, long count) {
        Number previous;
        if (parent.hasFacet(DOCUMENTS_COUNT_STATISTICS_FACET)) {
            previous = (Number) parent.getPropertyValue(DOCUMENTS_COUNT_STATISTICS_CHILDREN_COUNT_PROPERTY);
        } else {
            parent.addFacet(DOCUMENTS_COUNT_STATISTICS_FACET);
            previous = null;
        }
        DeltaLong childrenCount = DeltaLong.valueOf(previous, count);
        parent.setPropertyValue(DOCUMENTS_COUNT_STATISTICS_CHILDREN_COUNT_PROPERTY, childrenCount);
        // do not send notifications
        QuotaUtils.disableListeners(parent);
        DocumentModel origParent = parent;
        session.saveDocument(parent);
        QuotaUtils.clearContextData(origParent);
    }

    protected long getCount(DocumentModel doc) {
        if (doc.hasFacet(FOLDERISH)) {
            if (doc.hasFacet(DOCUMENTS_COUNT_STATISTICS_FACET)) {
                Number count = (Number) doc.getPropertyValue(DOCUMENTS_COUNT_STATISTICS_DESCENDANTS_COUNT_PROPERTY);
                return count == null ? 0 : count.longValue();
            } else {
                return 0;
            }
        } else {
            return 1;
        }
    }

    @Override
    public void computeInitialStatistics(CoreSession session, QuotaStatsInitialWork currentWorker) {
        Map<String, String> folders = getFolders(session);
        Map<String, Count> documentsCountByFolder = computeDocumentsCountByFolder(session, folders);
        saveDocumentsCount(session, documentsCountByFolder);
    }

    protected Map<String, String> getFolders(CoreSession session) {
        IterableQueryResult res = session.queryAndFetch(
                "SELECT ecm:uuid, ecm:parentId FROM Document WHERE ecm:mixinType = 'Folderish'", "NXQL");
        try {
            Map<String, String> folders = new HashMap<String, String>();

            for (Map<String, Serializable> r : res) {
                folders.put((String) r.get("ecm:uuid"), (String) r.get("ecm:parentId"));
            }
            return folders;
        } finally {
            if (res != null) {
                res.close();
            }
        }
    }

    protected Map<String, Count> computeDocumentsCountByFolder(CoreSession session, Map<String, String> folders) {
        IterableQueryResult res = session.queryAndFetch("SELECT ecm:uuid, ecm:parentId FROM Document", "NXQL");
        try {
            Map<String, Count> foldersCount = new HashMap<String, Count>();
            for (Map<String, Serializable> r : res) {
                String uuid = (String) r.get("ecm:uuid");
                if (folders.containsKey(uuid)) {
                    // a folder
                    continue;
                }

                String folderId = (String) r.get("ecm:parentId");
                if (!foldersCount.containsKey(folderId)) {
                    foldersCount.put(folderId, new Count());
                }
                Count count = foldersCount.get(folderId);
                count.childrenCount++;
                count.descendantsCount++;

                updateParentsDocumentsCount(folders, foldersCount, folderId);
            }
            return foldersCount;
        } finally {
            if (res != null) {
                res.close();
            }
        }
    }

    protected void updateParentsDocumentsCount(Map<String, String> folders, Map<String, Count> foldersCount,
            String folderId) {
        String parent = folders.get(folderId);
        while (parent != null) {
            if (!foldersCount.containsKey(parent)) {
                foldersCount.put(parent, new Count());
            }
            Count c = foldersCount.get(parent);
            c.descendantsCount++;
            parent = folders.get(parent);
        }
    }

    protected void saveDocumentsCount(CoreSession session, Map<String, Count> foldersCount) {
        long docsCount = 0;
        for (Map.Entry<String, Count> entry : foldersCount.entrySet()) {
            String folderId = entry.getKey();
            if (folderId == null) {
                continue;
            }
            DocumentModel folder;
            try {
                folder = session.getDocument(new IdRef(folderId));
            } catch (DocumentNotFoundException e) {
                log.warn(e);
                log.debug(e, e);
                continue;
            }
            if (folder.getPath().isRoot()) {
                // Root document
                continue;
            }
            saveDocumentsCount(session, folder, entry.getValue());
            docsCount++;
            if (docsCount % BATCH_SIZE == 0) {
                session.save();
                if (TransactionHelper.isTransactionActive()) {
                    TransactionHelper.commitOrRollbackTransaction();
                    TransactionHelper.startTransaction();
                }
            }
        }
        session.save();
    }

    protected void saveDocumentsCount(CoreSession session, DocumentModel folder, Count count) {
        if (!folder.hasFacet(DOCUMENTS_COUNT_STATISTICS_FACET)) {
            folder.addFacet(DOCUMENTS_COUNT_STATISTICS_FACET);
        }
        folder.setPropertyValue(DOCUMENTS_COUNT_STATISTICS_CHILDREN_COUNT_PROPERTY,
                Long.valueOf(count.childrenCount));
        folder.setPropertyValue(DOCUMENTS_COUNT_STATISTICS_DESCENDANTS_COUNT_PROPERTY,
                Long.valueOf(count.descendantsCount));
        // do not send notifications
        QuotaUtils.disableListeners(folder);
        DocumentModel origFolder = folder;
        session.saveDocument(folder);
        QuotaUtils.clearContextData(origFolder);
    }

    /**
     * Object to store documents count for a folder
     */
    private static class Count {

        public long childrenCount = 0;

        public long descendantsCount = 0;
    }

    @Override
    protected void processDocumentTrashOp(CoreSession session, DocumentModel doc, String transition) {
        // do nothing for count
    }

    @Override
    protected void processDocumentRestored(CoreSession session, DocumentModel doc) {
        // do nothing
    }

    @Override
    protected void processDocumentBeforeRestore(CoreSession session, DocumentModel doc) {
        // do nothing
    }
}