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}