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