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}