001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (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 GmbH & Co. KG, 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.jsp;
029
030import org.opencms.file.CmsObject;
031import org.opencms.file.CmsProperty;
032import org.opencms.file.CmsPropertyDefinition;
033import org.opencms.file.CmsResource;
034import org.opencms.file.CmsResourceFilter;
035import org.opencms.file.types.CmsResourceTypeFolder;
036import org.opencms.file.types.I_CmsResourceType;
037import org.opencms.main.CmsException;
038import org.opencms.main.CmsLog;
039import org.opencms.main.OpenCms;
040import org.opencms.util.CmsFileUtil;
041
042import java.util.ArrayList;
043import java.util.Collections;
044import java.util.List;
045import java.util.Locale;
046import java.util.Map;
047
048import org.apache.commons.logging.Log;
049
050/**
051 * Bean to provide a convenient way to build navigation structures based on the
052 * <code>{@link org.opencms.jsp.CmsJspNavElement}</code>.<p>
053 *
054 * Use this together with the <code>{@link org.opencms.jsp.CmsJspActionElement}</code>
055 * to obtain navigation information based on the current users permissions.
056 * For example, use <code>{@link #getNavigationForFolder(String)}</code> and pass the
057 * value of the current OpenCms user context uri obtained
058 * from <code>{@link org.opencms.file.CmsRequestContext#getUri()}</code> as argument to obtain a list
059 * of all items in the navigation of the current folder. Then use a simple scriptlet to
060 * iterate over these items and create a HTML navigation.<p>
061 *
062 * @since 6.0.0
063 *
064 * @see org.opencms.jsp.CmsJspNavElement
065 */
066public class CmsJspNavBuilder {
067
068    /**
069     * Navigation builder context.
070     *
071     * Stored in nav elements so they can ask the navigation builder for sub-navigation entries, with appropriate resource filter etc.
072     */
073    public static class NavContext {
074
075        /** The resource filter used. */
076        private CmsResourceFilter m_filter;
077
078        /** The nav builder used. */
079        private CmsJspNavBuilder m_navBuilder;
080
081        /** The visibility option used. */
082        private Visibility m_visibility;
083
084        /**
085         * Creates a new instance.
086         *
087         * @param navBuilder the navigation builder
088         * @param visibility the visibility
089         * @param filter the resource filter
090         */
091        public NavContext(CmsJspNavBuilder navBuilder, Visibility visibility, CmsResourceFilter filter) {
092
093            super();
094            m_visibility = visibility;
095            m_filter = filter;
096            m_navBuilder = navBuilder;
097        }
098
099        /**
100         * Gets the resource filter.
101         *
102         * @return the resource filter
103         */
104        public CmsResourceFilter getFilter() {
105
106            return m_filter;
107        }
108
109        /**
110         * Gets the navigation builder.
111         *
112         * @return the navigation builder
113         */
114        public CmsJspNavBuilder getNavBuilder() {
115
116            return m_navBuilder;
117        }
118
119        /**
120         * Gets the visibility setting.
121         *
122         * @return the visibility setting
123         */
124        public Visibility getVisibility() {
125
126            return m_visibility;
127        }
128
129    }
130
131    /** The visibility mode. */
132    public static enum Visibility {
133        /** All entries. */
134        all,
135        /** Navigation including hidden entries. */
136        includeHidden,
137        /** Navigation only. */
138        navigation
139    }
140
141    /** Default file property value to mark navigation level folders. */
142    public static final String NAVIGATION_LEVEL_FOLDER = "##navigation_level_folder##";
143
144    /** The log object for this class. */
145    private static final Log LOG = CmsLog.getLog(CmsJspNavBuilder.class);
146
147    /** The current CMS context. */
148    protected CmsObject m_cms;
149
150    /** The locale for which the property should be read. */
151    protected Locale m_locale;
152
153    /** The current request URI. */
154    protected String m_requestUri;
155
156    /** The current request folder. */
157    protected String m_requestUriFolder;
158
159    /**
160     * Empty constructor, so that this bean can be initialized from a JSP.<p>
161     */
162    public CmsJspNavBuilder() {
163
164        // empty
165    }
166
167    /**
168     * Default constructor.<p>
169     *
170     * @param cms context provider for the current request
171     */
172    public CmsJspNavBuilder(CmsObject cms) {
173
174        init(cms, null);
175    }
176
177    /**
178     * Constructor for a version that reads properties according to a locale.<p>
179     *
180     * @param cms context provider for the current request
181     * @param locale the locale for which properties should be accessed
182     */
183    public CmsJspNavBuilder(CmsObject cms, Locale locale) {
184
185        init(cms, locale);
186    }
187
188    /**
189     * Returns the full name (including VFS path) of the default file for this navigation element
190     * or <code>null</code> if the navigation element is not a folder.<p>
191     *
192     * The default file of a folder is determined by the value of the property
193     * <code>default-file</code> or the system wide property setting.<p>
194     *
195     * @param cms the CMS object
196     * @param folder full name of the folder
197     *
198     * @return the name of the default file
199     *
200     * @deprecated use {@link CmsObject#readDefaultFile(String)} instead
201     */
202    @Deprecated
203    public static String getDefaultFile(CmsObject cms, String folder) {
204
205        if (folder.endsWith("/")) {
206            try {
207                CmsResource defaultFile = cms.readDefaultFile(folder);
208                if (defaultFile != null) {
209                    return cms.getSitePath(defaultFile);
210                }
211            } catch (CmsException e) {
212                LOG.debug(e.getLocalizedMessage(), e);
213            }
214            return folder;
215        }
216        return null;
217    }
218
219    /**
220     * Collect all navigation elements from the files in the given folder,
221     * navigation elements are of class {@link CmsJspNavElement}.<p>
222     *
223     * @param cms context provider for the current request
224     * @param folder the selected folder
225     *
226     * @return a sorted (ascending to navigation position) list of navigation elements
227     *
228     * @deprecated use {@link #getNavigationForFolder(String)} instead
229     */
230    @Deprecated
231    public static List<CmsJspNavElement> getNavigationForFolder(CmsObject cms, String folder) {
232
233        return new CmsJspNavBuilder(cms).getNavigationForFolder(folder);
234    }
235
236    /**
237     * Build a navigation for the folder that is either minus levels up
238     * from the given folder, or that is plus levels down from the
239     * root folder towards the given folder.<p>
240     *
241     * If level is set to zero the root folder is used by convention.<p>
242     *
243     * @param cms context provider for the current request
244     * @param folder the selected folder
245     * @param level if negative, walk this many levels up, if positive, walk this many
246     *              levels down from root folder
247     *
248     * @return a sorted (ascending to navigation position) list of navigation elements
249     *
250     * @deprecated use {@link #getNavigationForFolder(String, int)} instead
251     */
252    @Deprecated
253    public static List<CmsJspNavElement> getNavigationForFolder(CmsObject cms, String folder, int level) {
254
255        return new CmsJspNavBuilder(cms).getNavigationForFolder(folder, level);
256    }
257
258    /**
259     * Returns a navigation element for the named resource.<p>
260     *
261     * @param cms context provider for the current request
262     * @param resource the resource name to get the navigation information for,
263     *              must be a full path name, e.g. "/docs/index.html"
264     *
265     * @return a navigation element for the given resource
266     *
267     * @deprecated use {@link #getNavigationForResource(String)} instead
268     */
269    @Deprecated
270    public static CmsJspNavElement getNavigationForResource(CmsObject cms, String resource) {
271
272        return new CmsJspNavBuilder(cms).getNavigationForResource(resource);
273    }
274
275    /**
276     * Builds a tree navigation for the folders between the provided start and end level.<p>
277     *
278     * A tree navigation includes all navigation elements that are required to display a tree structure.
279     * However, the data structure is a simple list.
280     * Each of the navigation elements in the list has the {@link CmsJspNavElement#getNavTreeLevel()} set
281     * to the level it belongs to. Use this information to distinguish between the navigation levels.<p>
282     *
283     * @param cms context provider for the current request
284     * @param folder the selected folder
285     * @param startlevel the start level
286     * @param endlevel the end level
287     *
288     * @return a sorted list of navigation elements with the navigation tree level property set
289     *
290     * @deprecated use {@link #getNavigationForResource(String)} instead
291     */
292    @Deprecated
293    public static List<CmsJspNavElement> getNavigationTreeForFolder(
294        CmsObject cms,
295        String folder,
296        int startlevel,
297        int endlevel) {
298
299        return new CmsJspNavBuilder(cms).getNavigationTreeForFolder(folder, startlevel, endlevel);
300    }
301
302    /**
303     * This method builds a complete navigation tree with entries of all branches
304     * from the specified folder.<p>
305     *
306     * For an unlimited depth of the navigation (i.e. no <code>endLevel</code>),
307     * set the <code>endLevel</code> to a value &lt; 0.<p>
308     *
309     *
310     * @param cms the current CMS context
311     * @param folder the root folder of the navigation tree
312     * @param endLevel the end level of the navigation
313     *
314     * @return list of navigation elements, in depth first order
315     *
316     * @deprecated use {@link #getNavigationForResource(String)} instead
317     */
318    @Deprecated
319    public static List<CmsJspNavElement> getSiteNavigation(CmsObject cms, String folder, int endLevel) {
320
321        return new CmsJspNavBuilder(cms).getSiteNavigation(folder, endLevel);
322    }
323
324    /**
325     * Returns whether the given resource is a folder and is marked to be a navigation level folder.<p>
326     *
327     * @param cms the cms context
328     * @param resource the resource
329     *
330     * @return <code>true</code> if the resource is marked to be a navigation level folder
331     */
332    public static boolean isNavLevelFolder(CmsObject cms, CmsResource resource) {
333
334        if (resource.isFolder()) {
335            I_CmsResourceType type = OpenCms.getResourceManager().getResourceType(resource);
336            if (CmsResourceTypeFolder.RESOURCE_TYPE_NAME.equals(type.getTypeName())) {
337                try {
338                    CmsProperty prop = cms.readPropertyObject(
339                        resource,
340                        CmsPropertyDefinition.PROPERTY_DEFAULT_FILE,
341                        false);
342                    return !prop.isNullProperty() && NAVIGATION_LEVEL_FOLDER.equals(prop.getValue());
343                } catch (CmsException e) {
344                    LOG.debug(e.getMessage(), e);
345                }
346            }
347        }
348        return false;
349    }
350
351    /**
352     * Gets the CMS context used for building the navigation.
353     *
354     * @return the CMS context
355     */
356    public CmsObject getCmsObject() {
357
358        return m_cms;
359    }
360
361    /**
362     * Build a "bread crumb" path navigation to the current folder.<p>
363     *
364     * @return ArrayList sorted list of navigation elements
365     *
366     * @see #getNavigationBreadCrumb(String, int, int, boolean)
367     */
368    public List<CmsJspNavElement> getNavigationBreadCrumb() {
369
370        return getNavigationBreadCrumb(m_requestUriFolder, 0, -1, true);
371    }
372
373    /**
374     * Build a "bread crumb" path navigation to the current folder.<p>
375     *
376     * @param startlevel the start level, if negative, go down |n| steps from selected folder
377     * @param currentFolder include the selected folder in navigation or not
378     *
379     * @return sorted list of navigation elements
380     *
381     * @see #getNavigationBreadCrumb(String, int, int, boolean)
382     */
383    public List<CmsJspNavElement> getNavigationBreadCrumb(int startlevel, boolean currentFolder) {
384
385        return getNavigationBreadCrumb(m_requestUriFolder, startlevel, -1, currentFolder);
386    }
387
388    /**
389     * Build a "bread crumb" path navigation to the current folder.<p>
390     *
391     * @param startlevel the start level, if negative, go down |n| steps from selected folder
392     * @param endlevel the end level, if -1, build navigation to selected folder
393     *
394     * @return sorted list of navigation elements
395     *
396     * @see #getNavigationBreadCrumb(String, int, int, boolean)
397     */
398    public List<CmsJspNavElement> getNavigationBreadCrumb(int startlevel, int endlevel) {
399
400        return getNavigationBreadCrumb(m_requestUriFolder, startlevel, endlevel, true);
401    }
402
403    /**
404     * Build a "bread crumb" path navigation to the given folder.<p>
405     *
406     * The <code>startlevel</code> marks the point where the navigation starts from, if negative,
407     * the count of steps to go down from the given folder.<p>
408     *
409     * The <code>endlevel</code> is the maximum level of the navigation path, set it to -1 to build the
410     * complete navigation to the given folder.<p>
411     *
412     * You can include the given folder in the navigation by setting <code>currentFolder</code> to
413     * <code>true</code>, otherwise <code>false</code>.<p>
414     *
415     * @param folder the selected folder
416     * @param startlevel the start level, if negative, go down |n| steps from selected folder
417     * @param endlevel the end level, if -1, build navigation to selected folder
418     * @param currentFolder include the selected folder in navigation or not
419     *
420     * @return sorted list of navigation elements
421     */
422    public List<CmsJspNavElement> getNavigationBreadCrumb(
423        String folder,
424        int startlevel,
425        int endlevel,
426        boolean currentFolder) {
427
428        List<CmsJspNavElement> result = new ArrayList<CmsJspNavElement>();
429
430        int level = CmsResource.getPathLevel(folder);
431        // decrease folder level if current folder is not displayed
432        if (!currentFolder) {
433            level -= 1;
434        }
435        // check current level and change endlevel if it is higher or -1
436        if ((level < endlevel) || (endlevel == -1)) {
437            endlevel = level;
438        }
439
440        // if startlevel is negative, display only |startlevel| links
441        if (startlevel < 0) {
442            startlevel = endlevel + startlevel + 1;
443            if (startlevel < 0) {
444                startlevel = 0;
445            }
446        }
447
448        // create the list of navigation elements
449        for (int i = startlevel; i <= endlevel; i++) {
450            String navFolder = CmsResource.getPathPart(folder, i);
451            CmsJspNavElement e = getNavigationForResource(navFolder);
452            if (e != null) {
453                // add element to list
454                result.add(e);
455            }
456        }
457
458        return result;
459    }
460
461    /**
462     * Collect all navigation elements from the files of the folder of the current request URI.<p>
463     *
464     * @return a sorted (ascending to navigation position) list of navigation elements
465     */
466    public List<CmsJspNavElement> getNavigationForFolder() {
467
468        return getNavigationForFolder(m_requestUriFolder);
469    }
470
471    /**
472     * Build a navigation for the folder that is either minus levels up
473     * from of the folder of the current request URI, or that is plus levels down from the
474     * root folder towards the current request URI.<p>
475     *
476     * If level is set to zero the root folder is used by convention.<p>
477     *
478     * @param level if negative, walk this many levels up, if positive, walk this many
479     *                  levels down from root folder
480     * @return a sorted (ascending to navigation position) list of navigation elements
481     */
482    public List<CmsJspNavElement> getNavigationForFolder(int level) {
483
484        return getNavigationForFolder(m_requestUriFolder, level);
485    }
486
487    /**
488     * Collect all navigation visible elements from the files in the given folder.<p>
489     *
490     * @param folder the selected folder
491     *
492     * @return A sorted (ascending to navigation position) list of navigation elements
493     */
494    public List<CmsJspNavElement> getNavigationForFolder(String folder) {
495
496        return getNavigationForFolder(folder, Visibility.navigation, CmsResourceFilter.DEFAULT);
497    }
498
499    /**
500     * Build a navigation for the folder that is either minus levels up
501     * from the given folder, or that is plus levels down from the
502     * root folder towards the given folder.<p>
503     *
504     * If level is set to zero the root folder is used by convention.<p>
505     *
506     * @param folder the selected folder
507     * @param level if negative, walk this many levels up, if positive, walk this many
508     *                  levels down from root folder
509     *
510     * @return a sorted (ascending to navigation position) list of navigation elements
511     */
512    public List<CmsJspNavElement> getNavigationForFolder(String folder, int level) {
513
514        folder = CmsResource.getFolderPath(folder);
515        // If level is one just use root folder
516        if (level == 0) {
517            return getNavigationForFolder("/");
518        }
519        String navfolder = CmsResource.getPathPart(folder, level);
520        // If navigation folder found use it to build navigation
521        if (navfolder != null) {
522            return getNavigationForFolder(navfolder);
523        }
524        // Nothing found, return empty list
525        return Collections.<CmsJspNavElement> emptyList();
526    }
527
528    /**
529     * Collect all navigation elements from the files in the given folder.<p>
530     *
531     * @param folder the selected folder
532     * @param visibility the visibility mode
533     * @param resourceFilter the filter to use reading the resources
534     *
535     * @return A sorted (ascending to navigation position) list of navigation elements
536     */
537    public List<CmsJspNavElement> getNavigationForFolder(
538        String folder,
539        Visibility visibility,
540        CmsResourceFilter resourceFilter) {
541
542        folder = CmsFileUtil.removeTrailingSeparator(folder);
543        List<CmsJspNavElement> result = new ArrayList<CmsJspNavElement>();
544
545        List<CmsResource> resources = null;
546        try {
547
548            resources = m_cms.getResourcesInFolder(folder, resourceFilter);
549        } catch (Exception e) {
550            // should never happen
551            LOG.error(e.getLocalizedMessage(), e);
552        }
553        if (resources == null) {
554            return Collections.<CmsJspNavElement> emptyList();
555        }
556        boolean includeAll = visibility == Visibility.all;
557        boolean includeHidden = visibility == Visibility.includeHidden;
558        for (CmsResource r : resources) {
559            CmsJspNavElement element = getNavigationForResource(m_cms.getSitePath(r), resourceFilter);
560            if ((element != null)
561                && (includeAll
562                    || (element.isInNavigation() && (includeHidden || !element.isHiddenNavigationEntry())))) {
563                element.setNavContext(new NavContext(this, visibility, resourceFilter));
564                result.add(element);
565            }
566        }
567        Collections.sort(result);
568        return result;
569    }
570
571    /**
572     * Returns a navigation element for the resource of the current request URI.<p>
573     *
574     * @return a navigation element for the resource of the current request URI
575     */
576    public CmsJspNavElement getNavigationForResource() {
577
578        return getNavigationForResource(m_requestUri);
579    }
580
581    /**
582     * Returns a navigation element for the named resource.<p>
583     *
584     * @param sitePath the resource name to get the navigation information for,
585     *              must be a full path name, e.g. "/docs/index.html"
586     *
587     * @return a navigation element for the given resource
588     */
589    public CmsJspNavElement getNavigationForResource(String sitePath) {
590
591        CmsJspNavElement result = getNavigationForResource(sitePath, CmsResourceFilter.DEFAULT, false);
592        if ((result != null) && (result.getNavContext() == null)) {
593            result.setNavContext(new NavContext(this, Visibility.navigation, CmsResourceFilter.DEFAULT));
594        }
595        return result;
596    }
597
598    /**
599     * Returns a navigation element for the named resource.<p>
600     *
601     * @param sitePath the resource name to get the navigation information for,
602     *              must be a full path name, e.g. "/docs/index.html"
603     * @param reourceFilter the resource filter
604     *
605     * @return a navigation element for the given resource
606     */
607    public CmsJspNavElement getNavigationForResource(String sitePath, CmsResourceFilter reourceFilter) {
608
609        return getNavigationForResource(sitePath, reourceFilter, false);
610    }
611
612    /**
613     * Builds a tree navigation for the folders between the provided start and end level.<p>
614     *
615     * @param startlevel the start level
616     * @param endlevel the end level
617     *
618     * @return a sorted list of navigation elements with the navigation tree level property set
619     *
620     * @see #getNavigationTreeForFolder(String, int, int)
621     */
622    public List<CmsJspNavElement> getNavigationTreeForFolder(int startlevel, int endlevel) {
623
624        return getNavigationTreeForFolder(m_requestUriFolder, startlevel, endlevel);
625    }
626
627    /**
628     * Builds a tree navigation for the folders between the provided start and end level.<p>
629     *
630     * @param folder the selected folder
631     * @param startlevel the start level
632     * @param endlevel the end level
633     *
634     * @return a sorted list of navigation elements with the navigation tree level property set
635     */
636    public List<CmsJspNavElement> getNavigationTreeForFolder(String folder, int startlevel, int endlevel) {
637
638        folder = CmsResource.getFolderPath(folder);
639        // Make sure start and end level make sense
640        if (endlevel < startlevel) {
641            return Collections.<CmsJspNavElement> emptyList();
642        }
643        int currentlevel = CmsResource.getPathLevel(folder);
644        if (currentlevel < endlevel) {
645            endlevel = currentlevel;
646        }
647        if (startlevel == endlevel) {
648            return getNavigationForFolder(CmsResource.getPathPart(folder, startlevel), startlevel);
649        }
650
651        List<CmsJspNavElement> result = new ArrayList<CmsJspNavElement>();
652        float parentcount = 0;
653
654        for (int i = startlevel; i <= endlevel; i++) {
655            String currentfolder = CmsResource.getPathPart(folder, i);
656            List<CmsJspNavElement> entries = getNavigationForFolder(currentfolder);
657            // Check for parent folder
658            if (parentcount > 0) {
659                for (CmsJspNavElement e : entries) {
660                    e.setNavPosition(e.getNavPosition() + parentcount);
661                }
662            }
663            // Add new entries to result
664            result.addAll(entries);
665            Collections.sort(result);
666            // Finally spread the values of the navigation items so that there is enough room for further items
667            float pos = 0;
668            int count = 0;
669            String nextfolder = CmsResource.getPathPart(folder, i + 1);
670            parentcount = 0;
671            for (CmsJspNavElement e : result) {
672                pos = 10000 * (++count);
673                e.setNavPosition(pos);
674                if (e.getResourceName().startsWith(nextfolder)) {
675                    parentcount = pos;
676                }
677            }
678            if (parentcount == 0) {
679                parentcount = pos;
680            }
681        }
682        return result;
683    }
684
685    /**
686     * This method builds a complete site navigation tree with entries of all branches.<p>
687     *
688     * @see #getSiteNavigation(String, int)
689     *
690     * @return list of navigation elements, in depth first order
691     */
692    public List<CmsJspNavElement> getSiteNavigation() {
693
694        return getSiteNavigation("/", Visibility.navigation, -1);
695    }
696
697    /**
698     * This method builds a complete navigation tree with entries of all branches
699     * from the specified folder.<p>
700     *
701     * @param folder folder the root folder of the navigation tree
702     * @param endLevel the end level of the navigation
703     *
704     * @return list of navigation elements, in depth first order
705     */
706    public List<CmsJspNavElement> getSiteNavigation(String folder, int endLevel) {
707
708        return getSiteNavigation(folder, Visibility.navigation, endLevel);
709    }
710
711    /**
712     * This method builds a complete navigation tree with entries of all branches
713     * from the specified folder.<p>
714     *
715     * @param folder folder the root folder of the navigation tree
716     * @param visibility controls whether entries hidden from navigation or not in navigation at all should be included
717     * @param endLevel the end level of the navigation
718     *
719     * @return list of navigation elements, in depth first order
720     */
721    public List<CmsJspNavElement> getSiteNavigation(String folder, Visibility visibility, int endLevel) {
722
723        folder = CmsFileUtil.addTrailingSeparator(folder);
724        // check if a specific end level was given, if not, build the complete navigation
725        boolean noLimit = false;
726        if (endLevel < 0) {
727            noLimit = true;
728        }
729        List<CmsJspNavElement> list = new ArrayList<CmsJspNavElement>();
730        // get the navigation for this folder
731        List<CmsJspNavElement> curnav = getNavigationForFolder(folder, visibility, CmsResourceFilter.DEFAULT);
732        // loop through all navigation entries
733        for (CmsJspNavElement ne : curnav) {
734            // add the navigation entry to the result list
735            list.add(ne);
736            // check if navigation entry is a folder or navigation level and below the max level -> if so, get the navigation from this folder as well
737            if ((ne.isFolderLink() || ne.isNavigationLevel()) && (noLimit || (ne.getNavTreeLevel() < endLevel))) {
738                List<CmsJspNavElement> subnav = getSiteNavigation(
739                    m_cms.getSitePath(ne.getResource()),
740                    visibility,
741                    endLevel);
742                // copy the result of the subfolder to the result list
743                list.addAll(subnav);
744            }
745        }
746        return list;
747    }
748
749    /**
750     * Initializes this bean.<p>
751     *
752     * @param cms the current cms context
753     */
754    public void init(CmsObject cms) {
755
756        init(cms, null, cms.getRequestContext().getUri());
757    }
758
759    /**
760     * Initializes this bean.<p>
761     *
762     * @param cms the current cms context
763     * @param locale the locale for which properties should be read
764     */
765    public void init(CmsObject cms, Locale locale) {
766
767        init(cms, locale, cms.getRequestContext().getUri());
768    }
769
770    /**
771     * Initializes this bean.<p>
772     *
773     * @param cms the current cms context
774     * @param locale the locale for which properties should be read
775     * @param requestUri the request URI
776     */
777    public void init(CmsObject cms, Locale locale, String requestUri) {
778
779        m_cms = cms;
780        m_locale = locale;
781        m_requestUri = requestUri;
782        m_requestUriFolder = CmsResource.getFolderPath(m_requestUri);
783    }
784
785    /**
786     * Collect all navigation elements from the files in the given folder.<p>
787    *
788    * @param folder the selected folder
789    * @param includeInvisible <code>true</code> to include elements not visible in navigation
790    * @param resourceFilter the filter to use reading the resources
791    * @param shallow <code>true</code> for a shallow look up, not regarding next level resources
792    *
793    * @return A sorted (ascending to navigation position) list of navigation elements
794    */
795    private List<CmsJspNavElement> getNavigationForFolder(
796        String folder,
797        boolean includeInvisible,
798        CmsResourceFilter resourceFilter,
799        boolean shallow) {
800
801        folder = CmsResource.getFolderPath(folder);
802        List<CmsJspNavElement> result = new ArrayList<CmsJspNavElement>();
803
804        List<CmsResource> resources;
805        try {
806            resources = m_cms.getResourcesInFolder(folder, resourceFilter);
807        } catch (Exception e) {
808            // should never happen
809            LOG.error(e.getLocalizedMessage(), e);
810            return Collections.<CmsJspNavElement> emptyList();
811        }
812
813        for (CmsResource r : resources) {
814            CmsJspNavElement element = getNavigationForResource(m_cms.getSitePath(r), resourceFilter, shallow);
815            if ((element != null) && (includeInvisible || element.isInNavigation())) {
816                result.add(element);
817            }
818        }
819        Collections.sort(result);
820        return result;
821    }
822
823    /**
824     * Returns a navigation element for the named resource.<p>
825     *
826     * @param sitePath the resource name to get the navigation information for,
827     *              must be a full path name, e.g. "/docs/index.html"
828     * @param resourceFilter the filter to use reading the resources
829     * @param shallow <code>true</code> for a shallow look up, not regarding next level resources
830     *
831     * @return a navigation element for the given resource
832     */
833    private CmsJspNavElement getNavigationForResource(
834        String sitePath,
835        CmsResourceFilter resourceFilter,
836        boolean shallow) {
837
838        CmsResource resource;
839        Map<String, String> propertiesMap;
840        int level = CmsResource.getPathLevel(sitePath);
841        if (sitePath.endsWith("/")) {
842            level--;
843        }
844        try {
845            resource = m_cms.readResource(sitePath, resourceFilter);
846            List<CmsProperty> properties = m_cms.readPropertyObjects(resource, false);
847            propertiesMap = CmsProperty.toMap(properties);
848            if (resource.isFolder()) {
849                if (resourceFilter.equals(CmsResourceFilter.DEFAULT)
850                    && !NAVIGATION_LEVEL_FOLDER.equals(
851                        propertiesMap.get(CmsPropertyDefinition.PROPERTY_DEFAULT_FILE))) {
852                    try {
853                        CmsResource defaultFile = m_cms.readDefaultFile(resource, resourceFilter);
854                        if ((defaultFile != null)
855                            && !defaultFile.isReleasedAndNotExpired(m_cms.getRequestContext().getRequestTime())) {
856                            // do not show navigation entries for unreleased or expired resources
857                            return null;
858                        }
859                    } catch (@SuppressWarnings("unused") CmsException e) {
860                        // may happen if permissions are not sufficient can be ignored
861                    }
862                }
863                if (!sitePath.endsWith("/")) {
864                    sitePath = sitePath + "/";
865                }
866                if (!shallow
867                    && (NAVIGATION_LEVEL_FOLDER.equals(
868                        propertiesMap.get(CmsPropertyDefinition.PROPERTY_DEFAULT_FILE)))) {
869                    // this folder is marked as a navigation level, set the site path to the first sub element
870                    List<CmsJspNavElement> subElements = getNavigationForFolder(sitePath, false, resourceFilter, true);
871                    if (!subElements.isEmpty()) {
872                        CmsJspNavElement subElement = subElements.get(0);
873                        subElement = getNavigationForResource(subElement.getSitePath(), resourceFilter, false);
874                        sitePath = subElement.getSitePath();
875                    }
876                }
877            }
878        } catch (Exception e) {
879            // may happen if permissions are not sufficient
880            LOG.warn(e.getLocalizedMessage(), e);
881            return null;
882        }
883
884        return new CmsJspNavElement(sitePath, resource, propertiesMap, level, m_locale);
885    }
886}