001/*
002 * (C) Copyright 2013 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 *     Martin Pernollet
018 */
019
020package org.nuxeo.ecm.platform.groups.audit.service.acl.data;
021
022import java.io.IOException;
023import java.util.ArrayList;
024import java.util.Collection;
025import java.util.HashSet;
026import java.util.Set;
027import java.util.SortedSet;
028import java.util.TreeSet;
029
030import org.apache.commons.logging.Log;
031import org.apache.commons.logging.LogFactory;
032import org.nuxeo.common.utils.Path;
033import org.nuxeo.ecm.core.api.CoreSession;
034import org.nuxeo.ecm.core.api.DocumentModel;
035import org.nuxeo.ecm.core.api.DocumentModelList;
036import org.nuxeo.ecm.core.api.NuxeoException;
037import org.nuxeo.ecm.platform.groups.audit.service.acl.Pair;
038import org.nuxeo.ecm.platform.groups.audit.service.acl.filter.IContentFilter;
039
040import com.google.common.collect.Multimap;
041
042/**
043 * Gather various data and statistics about a document tree
044 *
045 * @author Martin Pernollet <mpernollet@nuxeo.com>
046 */
047public class DataProcessor implements IDataProcessor {
048    protected static Log log = LogFactory.getLog(DataProcessor.class);
049
050    protected int documentMinDepth;
051
052    protected int documentTreeDepth;
053
054    protected SortedSet<String> userAndGroups;
055
056    protected SortedSet<String> permissions;
057
058    protected ProcessorStatus status;
059
060    protected String information;
061
062    protected Collection<DocumentSummary> allDocuments;
063
064    protected IContentFilter filter;
065
066    protected AclSummaryExtractor acl;
067
068    public enum ProcessorStatus {
069        SUCCESS, ERROR_TOO_MANY_DOCUMENTS, ERROR_TOO_LONG_PROCESS, ERROR
070    }
071
072    protected int n;
073
074    protected TicToc t = new TicToc();
075
076    /* */
077
078    public DataProcessor(IContentFilter filter) {
079        this.filter = filter;
080        this.acl = new AclSummaryExtractor(filter);
081    }
082
083    @Override
084    public void analyze(CoreSession session) {
085        analyze(session, session.getRootDocument(), 0);
086    }
087
088    @Override
089    public void analyze(CoreSession session, DocumentModel doc, int timeout) {
090        init();
091        doAnalyze(session, doc, timeout);
092        log();
093    }
094
095    public void init() {
096        userAndGroups = new TreeSet<String>();
097        permissions = new TreeSet<String>();
098        documentMinDepth = Integer.MAX_VALUE;
099        documentTreeDepth = 0;
100    }
101
102    // timeout ignored
103    protected void doAnalyze(CoreSession session, DocumentModel root, int timeout) {
104        // get data
105        final DataFetch fetch = new DataFetch();
106        DocumentModelList list;
107        try {
108            list = fetch.getAllChildren(session, root);
109        } catch (IOException e) {
110            throw new NuxeoException(e);
111        }
112        initSummarySet();
113
114        processDocument(root);
115
116        n = list.size();
117        t.tic();
118        for (DocumentModel d : list) {
119            processDocument(d);
120        }
121        status = ProcessorStatus.SUCCESS;
122    }
123
124    protected void initSummarySet() {
125        allDocuments = new ArrayList<DocumentSummary>(1000);
126    }
127
128    /**
129     * Extract relevant information from document model, to only keep a {@link DocumentSummary} and a few general
130     * informations about the document repository.
131     */
132    protected void processDocument(DocumentModel doc) {
133        final DocumentSummary da = computeSummary(doc);
134        updateTreeSize(da);
135        computeGlobalAclSummary(doc);
136        allDocuments.add(da);
137        // log.debug(getNumberOfDocuments() + "/" + n + " documents, elapsed " +
138        // t.toc() + "s, docdepth:" + da.getDepth());
139    }
140
141    /** Extract usefull document information for report rendering */
142    protected DocumentSummary computeSummary(DocumentModel doc) {
143        String title = doc.getTitle();
144        String path = doc.getPathAsString();
145        if (path == null)
146            path = "";
147        int depth = computeDepth(doc);
148
149        boolean lock = acl.hasLockInheritanceACE(doc);
150        Multimap<String, Pair<String, Boolean>> aclLo = acl.getAclLocalByUser(doc);
151        Multimap<String, Pair<String, Boolean>> aclIn = acl.getAclInheritedByUser(doc);
152
153        DocumentSummary da = new DocumentSummary(title, depth, lock, aclLo, aclIn, path);
154        return da;
155    }
156
157    protected int computeDepth(DocumentModel m) {
158        Path path = m.getPath();
159        return path.segmentCount();
160    }
161
162    /** report global tree size */
163    protected int updateTreeSize(DocumentSummary da) {
164        int depth = da.getDepth();
165        if (depth > documentTreeDepth)
166            documentTreeDepth = depth;
167        if (depth < documentMinDepth)
168            documentMinDepth = depth;
169        return depth;
170    }
171
172    /** store set of users and set of permission types */
173    protected void computeGlobalAclSummary(DocumentModel doc) {
174        Pair<HashSet<String>, HashSet<String>> s = acl.getAclSummary(doc);
175        userAndGroups.addAll(s.a);
176        permissions.addAll(s.b);
177    }
178
179    /* RESULTS */
180
181    /** Ranked so that appear like a tree. */
182    @Override
183    public Collection<DocumentSummary> getAllDocuments() {
184        return allDocuments;
185    }
186
187    @Override
188    public Set<String> getUserAndGroups() {
189        return userAndGroups;
190    }
191
192    @Override
193    public Set<String> getPermissions() {
194        return permissions;
195    }
196
197    @Override
198    public int getDocumentTreeMaxDepth() {
199        return documentTreeDepth;
200    }
201
202    @Override
203    public int getDocumentTreeMinDepth() {
204        return documentMinDepth;
205    }
206
207    @Override
208    public int getNumberOfDocuments() {
209        return allDocuments.size();
210    }
211
212    @Override
213    public ProcessorStatus getStatus() {
214        return status;
215    }
216
217    @Override
218    public String getInformation() {
219        return information;
220    }
221
222    /* */
223
224    public void log() {
225        log.debug("doc tree depth    : " + getDocumentTreeMaxDepth());
226        log.debug("#docs (or folders): "
227                + getNumberOfDocuments()
228                + " (analyzed by processor, may differ from actual number of doc in repo if exceeding timeout or max number of doc)");
229        log.debug("#users (or groups): " + getUserAndGroups().size()
230                + " (mentionned in ACLs, may differ from actual user directory)");
231        log.debug("#permissions types: " + getPermissions().size() + " (mentionned in ACLs)");
232    }
233}