001/*
002 * (C) Copyright 2006-2007 Nuxeo SAS (http://nuxeo.com/) and contributors.
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.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 *     Nuxeo - initial API and implementation
016 *
017 * $Id$
018 */
019package org.nuxeo.ecm.webapp.tree;
020
021import java.io.Serializable;
022import java.util.ArrayList;
023import java.util.Collections;
024import java.util.HashMap;
025import java.util.LinkedHashMap;
026import java.util.List;
027import java.util.Map;
028
029import org.apache.commons.logging.Log;
030import org.apache.commons.logging.LogFactory;
031import org.jboss.seam.Component;
032import org.jboss.seam.ScopeType;
033import org.nuxeo.ecm.core.api.CoreSession;
034import org.nuxeo.ecm.core.api.DocumentModel;
035import org.nuxeo.ecm.core.api.Filter;
036import org.nuxeo.ecm.core.api.SortInfo;
037import org.nuxeo.ecm.core.api.Sorter;
038import org.nuxeo.ecm.core.api.quota.QuotaStats;
039import org.nuxeo.ecm.core.api.quota.QuotaStatsNonFolderishCount;
040import org.nuxeo.ecm.core.api.security.SecurityConstants;
041import org.nuxeo.ecm.core.schema.FacetNames;
042import org.nuxeo.ecm.platform.contentview.jsf.ContentView;
043import org.nuxeo.ecm.platform.query.api.PageProvider;
044import org.nuxeo.ecm.platform.query.api.PageProviderService;
045import org.nuxeo.ecm.platform.query.nxql.CoreQueryDocumentPageProvider;
046import org.nuxeo.runtime.api.Framework;
047
048/**
049 * Tree node of documents.
050 * <p>
051 * Children are lazy-loaded from backend only when needed.
052 *
053 * @author Anahide Tchertchian
054 */
055public class DocumentTreeNodeImpl implements DocumentTreeNode {
056
057    private static final long serialVersionUID = 1L;
058
059    private static final Log log = LogFactory.getLog(DocumentTreeNodeImpl.class);
060
061    protected final DocumentModel document;
062
063    protected final Filter filter;
064
065    protected final Filter leafFilter;
066
067    protected final Sorter sorter;
068
069    protected final String pageProviderName;
070
071    protected ContentView orderableContentView;
072
073    protected Map<Object, DocumentTreeNodeImpl> children;
074
075    protected Boolean expanded = null;
076
077    public DocumentTreeNodeImpl(DocumentModel document, Filter filter, Filter leafFilter, Sorter sorter,
078            String pageProviderName) {
079        this.document = document;
080        this.filter = filter;
081        this.leafFilter = leafFilter;
082        this.sorter = sorter;
083        this.pageProviderName = pageProviderName;
084    }
085
086    /**
087     * @deprecated since 5.9.1, sessionId not used.
088     */
089    @Deprecated
090    public DocumentTreeNodeImpl(String sessionId, DocumentModel document, Filter filter, Filter leafFilter,
091            Sorter sorter, String pageProviderName) {
092        this(document, filter, leafFilter, sorter, pageProviderName);
093    }
094
095    /**
096     * @deprecated since 5.9.1, sessionId not used.
097     */
098    @Deprecated
099    public DocumentTreeNodeImpl(String sessionId, DocumentModel document, Filter filter, Sorter sorter) {
100        this(document, filter, null, sorter, null);
101    }
102
103    public DocumentTreeNodeImpl(DocumentModel document, Filter filter, Sorter sorter) {
104        this(document, filter, null, sorter, (String) null);
105    }
106
107    public List<DocumentTreeNode> getChildren() {
108        if (children == null) {
109            fetchChildren();
110        }
111        List<DocumentTreeNode> childrenNodes = new ArrayList<DocumentTreeNode>();
112        childrenNodes.addAll(children.values());
113        return childrenNodes;
114    }
115
116    public DocumentModel getDocument() {
117        return document;
118    }
119
120    public String getId() {
121        if (document != null) {
122            return document.getId();
123        }
124        return null;
125    }
126
127    public boolean isSelected(DocumentModel currentDocument) {
128        if (currentDocument != null) {
129            if (!currentDocument.isFolder()) {
130                // check if it's the closest parent
131                String currentPath = currentDocument.getPathAsString();
132                String nodePath = getPath();
133                if (currentPath != null && nodePath != null && currentPath.startsWith(nodePath)
134                        && currentPath.length() > nodePath.length()
135                        && currentPath.substring(nodePath.length()).startsWith("/") // make sure nodePath is the parent of currentPath
136                        && !currentPath.substring(nodePath.length() + 1).contains("/")) {
137                    // direct parent
138                    return true;
139                }
140            } else {
141                // check equality
142                return currentDocument.getId().equals(getId());
143            }
144        }
145        return false;
146    }
147
148    public String getPath() {
149        if (document != null) {
150            return document.getPathAsString();
151        }
152        return null;
153    }
154
155    /**
156     * Resets children map
157     */
158    public void resetChildren() {
159        children = null;
160    }
161
162    @SuppressWarnings("unchecked")
163    public void fetchChildren() {
164        children = new LinkedHashMap<Object, DocumentTreeNodeImpl>();
165        if (leafFilter != null && leafFilter.accept(document)) {
166            // filter says this is a leaf, don't look at children
167            return;
168        }
169        CoreSession session = getCoreSession();
170        if (session == null) {
171            log.error("Cannot retrieve CoreSession for " + document);
172            return;
173        }
174        List<DocumentModel> documents;
175        boolean isOrderable = document.hasFacet(FacetNames.ORDERABLE);
176        if (pageProviderName == null) {
177            // get the children using the core
178            Sorter sorterToUse = isOrderable ? null : sorter;
179            documents = session.getChildren(document.getRef(), null, SecurityConstants.READ, filter, sorterToUse);
180        } else {
181            // use page providers
182            PageProviderService ppService = Framework.getService(PageProviderService.class);
183            List<SortInfo> sortInfos = null;
184            Map<String, Serializable> props = new HashMap<String, Serializable>();
185            props.put(CoreQueryDocumentPageProvider.CORE_SESSION_PROPERTY, (Serializable) session);
186            if (isOrderable) {
187                // override sort infos to get default sort
188                sortInfos = new ArrayList<SortInfo>();
189                sortInfos.add(new SortInfo("ecm:pos", true));
190            }
191            PageProvider<DocumentModel> pp = (PageProvider<DocumentModel>) ppService.getPageProvider(pageProviderName,
192                    sortInfos, null, null, props, new Object[] { getId() });
193            documents = pp.getCurrentPage();
194            documents = filterAndSort(documents, !isOrderable);
195        }
196        // build the children nodes
197        for (DocumentModel child : documents) {
198            String identifier = child.getId();
199            DocumentTreeNodeImpl childNode;
200            childNode = new DocumentTreeNodeImpl(child, filter, leafFilter, sorter, pageProviderName);
201            children.put(identifier, childNode);
202        }
203    }
204
205    protected CoreSession getCoreSession() {
206        CoreSession session = document.getCoreSession();
207        if (session == null) {
208            session = (CoreSession) Component.getInstance("documentManager", ScopeType.CONVERSATION);
209        }
210        return session;
211    }
212
213    protected List<DocumentModel> filterAndSort(List<DocumentModel> docs, boolean doSort) {
214        // filter and sort if defined
215        List<DocumentModel> res = new ArrayList<DocumentModel>();
216        if (docs != null) {
217            if (filter == null) {
218                res.addAll(docs);
219            } else {
220                for (DocumentModel doc : docs) {
221                    if (filter.accept(doc)) {
222                        res.add(doc);
223                    }
224                }
225            }
226        }
227        if (sorter != null && doSort) {
228            Collections.sort(res, sorter);
229        }
230        return res;
231    }
232
233    @Override
234    public QuotaStats getQuotaStats() {
235        return document != null ? document.getAdapter(QuotaStatsNonFolderishCount.class) : null;
236    }
237
238    @Override
239    public boolean isExpanded() {
240        if (expanded == null) {
241            final TreeActions treeActionBean = (TreeActionsBean) Component.getInstance("treeActions");
242            if (!treeActionBean.isNodeExpandEvent()) {
243                String currentDocPath = treeActionBean.getCurrentDocumentPath();
244                if (currentDocPath != null && getPath() != null && currentDocPath.startsWith(getPath())) {
245                    // additional slower check for strict path prefix
246                    if ((currentDocPath + '/').startsWith(getPath() + '/') || "/".equals(getPath())) {
247                        expanded = Boolean.TRUE;
248                    }
249                }
250            }
251        }
252        return Boolean.TRUE.equals(expanded);
253    }
254
255    @Override
256    public void setExpanded(boolean expanded) {
257        this.expanded = Boolean.valueOf(expanded);
258    }
259
260}