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