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}