001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (C) Alkacon Software (http://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software, please see the 018 * company website: http://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: http://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.ade.galleries; 029 030import org.opencms.ade.galleries.shared.I_CmsGalleryTreeEntry; 031import org.opencms.file.CmsObject; 032import org.opencms.file.CmsResource; 033import org.opencms.file.CmsResourceFilter; 034import org.opencms.file.CmsVfsResourceNotFoundException; 035import org.opencms.main.CmsException; 036import org.opencms.main.OpenCms; 037import org.opencms.site.CmsSiteManagerImpl; 038import org.opencms.util.CmsStringUtil; 039 040import java.util.ArrayList; 041import java.util.Collection; 042import java.util.HashMap; 043import java.util.HashSet; 044import java.util.List; 045import java.util.Map; 046import java.util.Set; 047 048import com.google.common.base.Objects; 049import com.google.common.collect.ArrayListMultimap; 050import com.google.common.collect.Multimap; 051import com.google.common.collect.Sets; 052 053/** 054 * Abstract class which is used to generate the data for showing an already opened tree in the gallery dialog.<p> 055 * 056 * @param <T> the type of tree entry bean produced by this class 057 */ 058public abstract class A_CmsTreeTabDataPreloader<T extends I_CmsGalleryTreeEntry<T>> { 059 060 /** Keeps track of which resources are children of each other. */ 061 private Multimap<CmsResource, CmsResource> m_childMap = ArrayListMultimap.create(); 062 063 /** The CMS context used for file operations. */ 064 private CmsObject m_cms; 065 066 /** The common root path. */ 067 private String m_commonRoot; 068 069 /** The filter used for reading resources. */ 070 private CmsResourceFilter m_filter = CmsResourceFilter.ONLY_VISIBLE_NO_DELETED; 071 072 /** The set of resources which have already been read. */ 073 private Set<CmsResource> m_knownResources = new HashSet<CmsResource>(); 074 075 /** Resources whose children should be loaded. */ 076 private Set<CmsResource> m_mustLoadChildren = new HashSet<CmsResource>(); 077 078 /** The root resource. */ 079 private CmsResource m_rootResource; 080 081 /** 082 * Creates the preload data for a collection of resources which correspond to "opened" tree items.<p> 083 * 084 * @param cms the CMS context to use 085 * @param openResources the resources which correspond to opened tree items 086 * @param selectedResources resources which should be part of the tree, but not opened 087 * 088 * @return the root tree entry bean which was created 089 * 090 * @throws CmsException if something goes wrong 091 */ 092 public T preloadData(CmsObject cms, Set<CmsResource> openResources, Set<CmsResource> selectedResources) 093 throws CmsException { 094 095 assert m_cms == null : "Instance can't be used more than once!"; 096 if (openResources == null) { 097 openResources = Sets.newHashSet(); 098 } 099 if (selectedResources == null) { 100 selectedResources = Sets.newHashSet(); 101 } 102 103 boolean ignoreOpen = false; 104 if (!selectedResources.isEmpty() && !openResources.isEmpty()) { 105 // if selected and opened resources are in different sites, 106 // we do *not* want to start from the common root folder (usually '/'), 107 // so ignore the 'open' resources. 108 String siteForSelected = getCommonSite(selectedResources); 109 String siteForOpen = getCommonSite(openResources); 110 if (!Objects.equal(siteForSelected, siteForOpen)) { 111 ignoreOpen = true; 112 } 113 } 114 115 Set<CmsResource> allParamResources = Sets.newHashSet(); 116 if (!ignoreOpen) { 117 allParamResources.addAll(openResources); 118 } 119 allParamResources.addAll(selectedResources); 120 m_cms = OpenCms.initCmsObject(cms); 121 m_cms.getRequestContext().setSiteRoot(""); 122 // first determine the common root of all open resources 123 findRoot(allParamResources); 124 125 m_mustLoadChildren.add(m_rootResource); 126 m_mustLoadChildren.addAll(openResources); 127 // now load ancestors of all open resources 128 for (CmsResource resource : allParamResources) { 129 loadAncestors(resource); 130 } 131 // ensure that all children of ancestors of open resources are loaded 132 loadChildren(); 133 // finally create the beans for the loaded resources 134 return createBeans(); 135 } 136 137 /** 138 * Creates a tree entry bean from a resource.<p> 139 * 140 * @param cms the current CMS context 141 * @param resource the resource for which to create the tree entry bean 142 * 143 * @return the created tree entry bean 144 * @throws CmsException if something goes wrong 145 */ 146 protected abstract T createEntry(CmsObject cms, CmsResource resource) throws CmsException; 147 148 /** 149 * Finds the common root folder for a collection of resources.<p> 150 * 151 * @param resources the collection of resources 152 * 153 * @throws CmsException if something goes wrong 154 */ 155 protected void findRoot(Collection<CmsResource> resources) throws CmsException { 156 157 m_commonRoot = getCommonSite(resources); 158 String commonPath = getCommonAncestorPath(resources); 159 try { 160 m_rootResource = m_cms.readResource(m_commonRoot, m_filter); 161 } catch (CmsVfsResourceNotFoundException e) { 162 String currentPath = commonPath; 163 String lastWorkingPath = null; 164 while (m_cms.existsResource(currentPath, m_filter)) { 165 lastWorkingPath = currentPath; 166 currentPath = CmsResource.getParentFolder(currentPath); 167 } 168 m_rootResource = m_cms.readResource(lastWorkingPath, m_filter); 169 m_commonRoot = lastWorkingPath; 170 } 171 m_knownResources.add(m_rootResource); 172 } 173 174 /** 175 * Gets the children of a resource.<p> 176 * 177 * @param resource the resource for which the children should be read 178 * @return the children of the resource 179 * 180 * @throws CmsException if something goes wrong 181 */ 182 protected List<CmsResource> getChildren(CmsResource resource) throws CmsException { 183 184 return m_cms.getSubFolders(resource.getRootPath(), m_filter); 185 } 186 187 /** 188 * Gets the common site root of a set of resources.<p> 189 * @param resourceSet the resources 190 * 191 * @return the common site root (may also be the shared folder or root folder) 192 */ 193 protected String getCommonSite(Collection<CmsResource> resourceSet) { 194 195 String commonPath = getCommonAncestorPath(resourceSet); 196 String result = null; 197 CmsSiteManagerImpl siteManager = OpenCms.getSiteManager(); 198 if (siteManager.startsWithShared(commonPath)) { 199 result = siteManager.getSharedFolder(); 200 } else { 201 String siteRoot = siteManager.getSiteRoot(CmsStringUtil.joinPaths(commonPath, "/")); 202 if (siteRoot == null) { 203 result = "/"; 204 } else { 205 result = siteRoot; 206 } 207 } 208 return result; 209 210 } 211 212 /** 213 * Creates the beans for the loaded resources, and returns the root bean.<p> 214 * 215 * @return the root bean 216 * @throws CmsException if something goes wrong 217 */ 218 private T createBeans() throws CmsException { 219 220 // create the beans for the resources 221 Map<CmsResource, T> beans = new HashMap<CmsResource, T>(); 222 for (CmsResource resource : m_knownResources) { 223 T bean = createEntry(m_cms, resource); 224 if (bean != null) { 225 beans.put(resource, bean); 226 } 227 } 228 229 // attach beans for child resources to the beans for their parents 230 for (Map.Entry<CmsResource, T> entry : beans.entrySet()) { 231 CmsResource key = entry.getKey(); 232 T bean = entry.getValue(); 233 for (CmsResource child : m_childMap.get(key)) { 234 T childEntry = beans.get(child); 235 if (childEntry != null) { 236 bean.addChild(childEntry); 237 } 238 } 239 } 240 return beans.get(m_rootResource); 241 } 242 243 /** 244 * Gets the common ancestor path of a collection of resources.<p> 245 * 246 * @param resources the resources for which to get the ancestor path 247 * 248 * @return the common ancestor path for the resources 249 */ 250 private String getCommonAncestorPath(Collection<CmsResource> resources) { 251 252 if (resources.isEmpty()) { 253 return "/"; 254 } 255 String commonPath = null; 256 for (CmsResource resource : resources) { 257 commonPath = getCommonAncestorPath(commonPath, resource.getRootPath()); 258 } 259 return commonPath; 260 } 261 262 /** 263 * Gets the common ancestor of two paths.<p> 264 * 265 * @param rootPath1 the first path 266 * @param rootPath2 the second path 267 * 268 * @return the common ancestor path 269 */ 270 private String getCommonAncestorPath(String rootPath1, String rootPath2) { 271 272 if (rootPath1 == null) { 273 return rootPath2; 274 } 275 if (rootPath2 == null) { 276 return rootPath1; 277 } 278 rootPath1 = CmsStringUtil.joinPaths("/", rootPath1, "/"); 279 rootPath2 = CmsStringUtil.joinPaths("/", rootPath2, "/"); 280 int minLength = Math.min(rootPath1.length(), rootPath2.length()); 281 int i; 282 for (i = 0; i < minLength; i++) { 283 char char1 = rootPath1.charAt(i); 284 char char2 = rootPath2.charAt(i); 285 if (char1 != char2) { 286 break; 287 } 288 } 289 String result = rootPath1.substring(0, i); 290 if ("/".equals(result)) { 291 return result; 292 } 293 int slashIndex = result.lastIndexOf('/'); 294 result = result.substring(0, slashIndex); 295 return result; 296 } 297 298 /** 299 * Loads the ancestors of a resource.<p> 300 * 301 * @param resource the resource for which to load the ancestors 302 * 303 * @throws CmsException if something goes wrong 304 */ 305 private void loadAncestors(CmsResource resource) throws CmsException { 306 307 CmsResource currentResource = resource; 308 while ((currentResource != null) && m_cms.existsResource(currentResource.getStructureId(), m_filter)) { 309 if (!m_knownResources.add(currentResource)) { 310 break; 311 } 312 313 if (CmsStringUtil.comparePaths(currentResource.getRootPath(), m_commonRoot)) { 314 break; 315 } 316 CmsResource parent = m_cms.readParentFolder(currentResource.getStructureId()); 317 if (parent != null) { 318 m_mustLoadChildren.add(parent); 319 320 } 321 currentResource = parent; 322 } 323 } 324 325 /** 326 * Loads the children of the already loaded resources.<p> 327 * 328 * @throws CmsException if something goes wrong 329 */ 330 private void loadChildren() throws CmsException { 331 332 for (CmsResource resource : new ArrayList<CmsResource>(m_knownResources)) { 333 if (resource.isFolder()) { 334 if (!m_mustLoadChildren.contains(resource)) { 335 continue; 336 } 337 List<CmsResource> children = getChildren(resource); 338 for (CmsResource child : children) { 339 m_knownResources.add(child); 340 m_childMap.put(resource, child); 341 } 342 } 343 } 344 } 345 346}