content: /*
   * This library is part of OpenCms -
   * the Open Source Content Management System
   *
   * Copyright (c) Alkacon Software GmbH & Co. KG (https://www.alkacon.com)
   *
   * This library is free software; you can redistribute it and/or
   * modify it under the terms of the GNU Lesser General Public
   * License as published by the Free Software Foundation; either
   * version 2.1 of the License, or (at your option) any later version.
   *
   * This library is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
   * Lesser General Public License for more details.
   *
   * For further information about Alkacon Software GmbH & Co. KG, please see the
   * company website: https://www.alkacon.com
   *
   * For further information about OpenCms, please see the
   * project website: https://www.opencms.org
   *
   * You should have received a copy of the GNU Lesser General Public
   * License along with this library; if not, write to the Free Software
   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   */
  
  package org.opencms.jsp;
  
  import org.opencms.file.CmsObject;
  import org.opencms.file.CmsProperty;
  import org.opencms.file.CmsPropertyDefinition;
  import org.opencms.file.CmsResource;
  import org.opencms.file.CmsResourceFilter;
  import org.opencms.file.types.CmsResourceTypeFolder;
  import org.opencms.file.types.I_CmsResourceType;
  import org.opencms.main.CmsException;
  import org.opencms.main.CmsLog;
  import org.opencms.main.OpenCms;
  import org.opencms.util.CmsFileUtil;
  
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.List;
  import java.util.Locale;
  import java.util.Map;
  
  import org.apache.commons.logging.Log;
  
  /**
   * Bean to provide a convenient way to build navigation structures based on the
   * <code>{@link org.opencms.jsp.CmsJspNavElement}</code>.<p>
   *
   * Use this together with the <code>{@link org.opencms.jsp.CmsJspActionElement}</code>
   * to obtain navigation information based on the current users permissions.
   * For example, use <code>{@link #getNavigationForFolder(String)}</code> and pass the
   * value of the current OpenCms user context uri obtained
   * from <code>{@link org.opencms.file.CmsRequestContext#getUri()}</code> as argument to obtain a list
   * of all items in the navigation of the current folder. Then use a simple scriptlet to
   * iterate over these items and create a HTML navigation.<p>
   *
   * @since 6.0.0
   *
   * @see org.opencms.jsp.CmsJspNavElement
   */
  public class CmsJspNavBuilder {
  
      /**
       * Navigation builder context.
       *
       * Stored in nav elements so they can ask the navigation builder for sub-navigation entries, with appropriate resource filter etc.
       */
      public static class NavContext {
  
          /** The resource filter used. */
          private CmsResourceFilter m_filter;
  
          /** The nav builder used. */
          private CmsJspNavBuilder m_navBuilder;
  
          /** The visibility option used. */
          private Visibility m_visibility;
  
          /**
           * Creates a new instance.
           *
           * @param navBuilder the navigation builder
           * @param visibility the visibility
           * @param filter the resource filter
           */
          public NavContext(CmsJspNavBuilder navBuilder, Visibility visibility, CmsResourceFilter filter) {
  
              super();
              m_visibility = visibility;
              m_filter = filter;
              m_navBuilder = navBuilder;
          }
  
          /**
           * Gets the resource filter.
           *
           * @return the resource filter
           */
          public CmsResourceFilter getFilter() {
  
              return m_filter;
          }
  
          /**
           * Gets the navigation builder.
           *
           * @return the navigation builder
           */
          public CmsJspNavBuilder getNavBuilder() {
  
              return m_navBuilder;
          }
  
          /**
           * Gets the visibility setting.
           *
           * @return the visibility setting
           */
          public Visibility getVisibility() {
  
              return m_visibility;
          }
  
      }
  
      /** The visibility mode. */
      public static enum Visibility {
          /** All entries. */
          all,
          /** Navigation including hidden entries. */
          includeHidden,
          /** Navigation only. */
          navigation
      }
  
      /** Default file property value to mark navigation level folders. */
      public static final String NAVIGATION_LEVEL_FOLDER = "##navigation_level_folder##";
  
      /** The log object for this class. */
      private static final Log LOG = CmsLog.getLog(CmsJspNavBuilder.class);
  
      /** The current CMS context. */
      protected CmsObject m_cms;
  
      /** The locale for which the property should be read. */
      protected Locale m_locale;
  
      /** The current request URI. */
      protected String m_requestUri;
  
      /** The current request folder. */
      protected String m_requestUriFolder;
  
      /**
       * Empty constructor, so that this bean can be initialized from a JSP.<p>
       */
      public CmsJspNavBuilder() {
  
          // empty
      }
  
      /**
       * Default constructor.<p>
       *
       * @param cms context provider for the current request
       */
      public CmsJspNavBuilder(CmsObject cms) {
  
          init(cms, null);
      }
  
      /**
       * Constructor for a version that reads properties according to a locale.<p>
       *
       * @param cms context provider for the current request
       * @param locale the locale for which properties should be accessed
       */
      public CmsJspNavBuilder(CmsObject cms, Locale locale) {
  
          init(cms, locale);
      }
  
      /**
       * Returns the full name (including VFS path) of the default file for this navigation element
       * or <code>null</code> if the navigation element is not a folder.<p>
       *
       * The default file of a folder is determined by the value of the property
       * <code>default-file</code> or the system wide property setting.<p>
       *
       * @param cms the CMS object
       * @param folder full name of the folder
       *
       * @return the name of the default file
       *
       * @deprecated use {@link CmsObject#readDefaultFile(String)} instead
       */
      @Deprecated
      public static String getDefaultFile(CmsObject cms, String folder) {
  
          if (folder.endsWith("/")) {
              try {
                  CmsResource defaultFile = cms.readDefaultFile(folder);
                  if (defaultFile != null) {
                      return cms.getSitePath(defaultFile);
                  }
              } catch (CmsException e) {
                  LOG.debug(e.getLocalizedMessage(), e);
              }
              return folder;
          }
          return null;
      }
  
      /**
       * Collect all navigation elements from the files in the given folder,
       * navigation elements are of class {@link CmsJspNavElement}.<p>
       *
       * @param cms context provider for the current request
       * @param folder the selected folder
       *
       * @return a sorted (ascending to navigation position) list of navigation elements
       *
       * @deprecated use {@link #getNavigationForFolder(String)} instead
       */
      @Deprecated
      public static List<CmsJspNavElement> getNavigationForFolder(CmsObject cms, String folder) {
  
          return new CmsJspNavBuilder(cms).getNavigationForFolder(folder);
      }
  
      /**
       * Build a navigation for the folder that is either minus levels up
       * from the given folder, or that is plus levels down from the
       * root folder towards the given folder.<p>
       *
       * If level is set to zero the root folder is used by convention.<p>
       *
       * @param cms context provider for the current request
       * @param folder the selected folder
       * @param level if negative, walk this many levels up, if positive, walk this many
       *              levels down from root folder
       *
       * @return a sorted (ascending to navigation position) list of navigation elements
       *
       * @deprecated use {@link #getNavigationForFolder(String, int)} instead
       */
      @Deprecated
      public static List<CmsJspNavElement> getNavigationForFolder(CmsObject cms, String folder, int level) {
  
          return new CmsJspNavBuilder(cms).getNavigationForFolder(folder, level);
      }
  
      /**
       * Returns a navigation element for the named resource.<p>
       *
       * @param cms context provider for the current request
       * @param resource the resource name to get the navigation information for,
       *              must be a full path name, e.g. "/docs/index.html"
       *
       * @return a navigation element for the given resource
       *
       * @deprecated use {@link #getNavigationForResource(String)} instead
       */
      @Deprecated
      public static CmsJspNavElement getNavigationForResource(CmsObject cms, String resource) {
  
          return new CmsJspNavBuilder(cms).getNavigationForResource(resource);
      }
  
      /**
       * Builds a tree navigation for the folders between the provided start and end level.<p>
       *
       * A tree navigation includes all navigation elements that are required to display a tree structure.
       * However, the data structure is a simple list.
       * Each of the navigation elements in the list has the {@link CmsJspNavElement#getNavTreeLevel()} set
       * to the level it belongs to. Use this information to distinguish between the navigation levels.<p>
       *
       * @param cms context provider for the current request
       * @param folder the selected folder
       * @param startlevel the start level
       * @param endlevel the end level
       *
       * @return a sorted list of navigation elements with the navigation tree level property set
       *
       * @deprecated use {@link #getNavigationForResource(String)} instead
       */
      @Deprecated
      public static List<CmsJspNavElement> getNavigationTreeForFolder(
          CmsObject cms,
          String folder,
          int startlevel,
          int endlevel) {
  
          return new CmsJspNavBuilder(cms).getNavigationTreeForFolder(folder, startlevel, endlevel);
      }
  
      /**
       * This method builds a complete navigation tree with entries of all branches
       * from the specified folder.<p>
       *
       * For an unlimited depth of the navigation (i.e. no <code>endLevel</code>),
       * set the <code>endLevel</code> to a value &lt; 0.<p>
       *
       *
       * @param cms the current CMS context
       * @param folder the root folder of the navigation tree
       * @param endLevel the end level of the navigation
       *
       * @return list of navigation elements, in depth first order
       *
       * @deprecated use {@link #getNavigationForResource(String)} instead
       */
      @Deprecated
      public static List<CmsJspNavElement> getSiteNavigation(CmsObject cms, String folder, int endLevel) {
  
          return new CmsJspNavBuilder(cms).getSiteNavigation(folder, endLevel);
      }
  
      /**
       * Returns whether the given resource is a folder and is marked to be a navigation level folder.<p>
       *
       * @param cms the cms context
       * @param resource the resource
       *
       * @return <code>true</code> if the resource is marked to be a navigation level folder
       */
      public static boolean isNavLevelFolder(CmsObject cms, CmsResource resource) {
  
          if (resource.isFolder()) {
              I_CmsResourceType type = OpenCms.getResourceManager().getResourceType(resource);
              if (CmsResourceTypeFolder.RESOURCE_TYPE_NAME.equals(type.getTypeName())) {
                  try {
                      CmsProperty prop = cms.readPropertyObject(
                          resource,
                          CmsPropertyDefinition.PROPERTY_DEFAULT_FILE,
                          false);
                      return !prop.isNullProperty() && NAVIGATION_LEVEL_FOLDER.equals(prop.getValue());
                  } catch (CmsException e) {
                      LOG.debug(e.getMessage(), e);
                  }
              }
          }
          return false;
      }
  
      /**
       * Gets the CMS context used for building the navigation.
       *
       * @return the CMS context
       */
      public CmsObject getCmsObject() {
  
          return m_cms;
      }
  
      /**
       * Build a "bread crumb" path navigation to the current folder.<p>
       *
       * @return ArrayList sorted list of navigation elements
       *
       * @see #getNavigationBreadCrumb(String, int, int, boolean)
       */
      public List<CmsJspNavElement> getNavigationBreadCrumb() {
  
          return getNavigationBreadCrumb(m_requestUriFolder, 0, -1, true);
      }
  
      /**
       * Build a "bread crumb" path navigation to the current folder.<p>
       *
       * @param startlevel the start level, if negative, go down |n| steps from selected folder
       * @param currentFolder include the selected folder in navigation or not
       *
       * @return sorted list of navigation elements
       *
       * @see #getNavigationBreadCrumb(String, int, int, boolean)
       */
      public List<CmsJspNavElement> getNavigationBreadCrumb(int startlevel, boolean currentFolder) {
  
          return getNavigationBreadCrumb(m_requestUriFolder, startlevel, -1, currentFolder);
      }
  
      /**
       * Build a "bread crumb" path navigation to the current folder.<p>
       *
       * @param startlevel the start level, if negative, go down |n| steps from selected folder
       * @param endlevel the end level, if -1, build navigation to selected folder
       *
       * @return sorted list of navigation elements
       *
       * @see #getNavigationBreadCrumb(String, int, int, boolean)
       */
      public List<CmsJspNavElement> getNavigationBreadCrumb(int startlevel, int endlevel) {
  
          return getNavigationBreadCrumb(m_requestUriFolder, startlevel, endlevel, true);
      }
  
      /**
       * Build a "bread crumb" path navigation to the given folder.<p>
       *
       * The <code>startlevel</code> marks the point where the navigation starts from, if negative,
       * the count of steps to go down from the given folder.<p>
       *
       * The <code>endlevel</code> is the maximum level of the navigation path, set it to -1 to build the
       * complete navigation to the given folder.<p>
       *
       * You can include the given folder in the navigation by setting <code>currentFolder</code> to
       * <code>true</code>, otherwise <code>false</code>.<p>
       *
       * @param folder the selected folder
       * @param startlevel the start level, if negative, go down |n| steps from selected folder
       * @param endlevel the end level, if -1, build navigation to selected folder
       * @param currentFolder include the selected folder in navigation or not
       *
       * @return sorted list of navigation elements
       */
      public List<CmsJspNavElement> getNavigationBreadCrumb(
          String folder,
          int startlevel,
          int endlevel,
          boolean currentFolder) {
  
          List<CmsJspNavElement> result = new ArrayList<CmsJspNavElement>();
  
          int level = CmsResource.getPathLevel(folder);
          // decrease folder level if current folder is not displayed
          if (!currentFolder) {
              level -= 1;
          }
          // check current level and change endlevel if it is higher or -1
          if ((level < endlevel) || (endlevel == -1)) {
              endlevel = level;
          }
  
          // if startlevel is negative, display only |startlevel| links
          if (startlevel < 0) {
              startlevel = endlevel + startlevel + 1;
              if (startlevel < 0) {
                  startlevel = 0;
              }
          }
  
          // create the list of navigation elements
          for (int i = startlevel; i <= endlevel; i++) {
              String navFolder = CmsResource.getPathPart(folder, i);
              CmsJspNavElement e = getNavigationForResource(navFolder);
              if (e != null) {
                  // add element to list
                  result.add(e);
              }
          }
  
          return result;
      }
  
      /**
       * Collect all navigation elements from the files of the folder of the current request URI.<p>
       *
       * @return a sorted (ascending to navigation position) list of navigation elements
       */
      public List<CmsJspNavElement> getNavigationForFolder() {
  
          return getNavigationForFolder(m_requestUriFolder);
      }
  
      /**
       * Build a navigation for the folder that is either minus levels up
       * from of the folder of the current request URI, or that is plus levels down from the
       * root folder towards the current request URI.<p>
       *
       * If level is set to zero the root folder is used by convention.<p>
       *
       * @param level if negative, walk this many levels up, if positive, walk this many
       *                  levels down from root folder
       * @return a sorted (ascending to navigation position) list of navigation elements
       */
      public List<CmsJspNavElement> getNavigationForFolder(int level) {
  
          return getNavigationForFolder(m_requestUriFolder, level);
      }
  
      /**
       * Collect all navigation visible elements from the files in the given folder.<p>
       *
       * @param folder the selected folder
       *
       * @return A sorted (ascending to navigation position) list of navigation elements
       */
      public List<CmsJspNavElement> getNavigationForFolder(String folder) {
  
          return getNavigationForFolder(folder, Visibility.navigation, CmsResourceFilter.DEFAULT);
      }
  
      /**
       * Build a navigation for the folder that is either minus levels up
       * from the given folder, or that is plus levels down from the
       * root folder towards the given folder.<p>
       *
       * If level is set to zero the root folder is used by convention.<p>
       *
       * @param folder the selected folder
       * @param level if negative, walk this many levels up, if positive, walk this many
       *                  levels down from root folder
       *
       * @return a sorted (ascending to navigation position) list of navigation elements
       */
      public List<CmsJspNavElement> getNavigationForFolder(String folder, int level) {
  
          folder = CmsResource.getFolderPath(folder);
          // If level is one just use root folder
          if (level == 0) {
              return getNavigationForFolder("/");
          }
          String navfolder = CmsResource.getPathPart(folder, level);
          // If navigation folder found use it to build navigation
          if (navfolder != null) {
              return getNavigationForFolder(navfolder);
          }
          // Nothing found, return empty list
          return Collections.<CmsJspNavElement> emptyList();
      }
  
      /**
       * Collect all navigation elements from the files in the given folder.<p>
       *
       * @param folder the selected folder
       * @param visibility the visibility mode
       * @param resourceFilter the filter to use reading the resources
       *
       * @return A sorted (ascending to navigation position) list of navigation elements
       */
      public List<CmsJspNavElement> getNavigationForFolder(
          String folder,
          Visibility visibility,
          CmsResourceFilter resourceFilter) {
  
          folder = CmsFileUtil.removeTrailingSeparator(folder);
          List<CmsJspNavElement> result = new ArrayList<CmsJspNavElement>();
  
          List<CmsResource> resources = null;
          try {
  
              resources = m_cms.getResourcesInFolder(folder, resourceFilter);
          } catch (Exception e) {
              // should never happen
              LOG.error(e.getLocalizedMessage(), e);
          }
          if (resources == null) {
              return Collections.<CmsJspNavElement> emptyList();
          }
          boolean includeAll = visibility == Visibility.all;
          boolean includeHidden = visibility == Visibility.includeHidden;
          for (CmsResource r : resources) {
              CmsJspNavElement element = getNavigationForResource(m_cms.getSitePath(r), resourceFilter);
              if ((element != null)
                  && (includeAll
                      || (element.isInNavigation() && (includeHidden || !element.isHiddenNavigationEntry())))) {
                  element.setNavContext(new NavContext(this, visibility, resourceFilter));
                  result.add(element);
              }
          }
          Collections.sort(result);
          return result;
      }
  
      /**
       * Returns a navigation element for the resource of the current request URI.<p>
       *
       * @return a navigation element for the resource of the current request URI
       */
      public CmsJspNavElement getNavigationForResource() {
  
          return getNavigationForResource(m_requestUri);
      }
  
      /**
       * Returns a navigation element for the named resource.<p>
       *
       * @param sitePath the resource name to get the navigation information for,
       *              must be a full path name, e.g. "/docs/index.html"
       *
       * @return a navigation element for the given resource
       */
      public CmsJspNavElement getNavigationForResource(String sitePath) {
  
          CmsJspNavElement result = getNavigationForResource(sitePath, CmsResourceFilter.DEFAULT, false);
          if ((result != null) && (result.getNavContext() == null)) {
              result.setNavContext(new NavContext(this, Visibility.navigation, CmsResourceFilter.DEFAULT));
          }
          return result;
      }
  
      /**
       * Returns a navigation element for the named resource.<p>
       *
       * @param sitePath the resource name to get the navigation information for,
       *              must be a full path name, e.g. "/docs/index.html"
       * @param reourceFilter the resource filter
       *
       * @return a navigation element for the given resource
       */
      public CmsJspNavElement getNavigationForResource(String sitePath, CmsResourceFilter reourceFilter) {
  
          return getNavigationForResource(sitePath, reourceFilter, false);
      }
  
      /**
       * Builds a tree navigation for the folders between the provided start and end level.<p>
       *
       * @param startlevel the start level
       * @param endlevel the end level
       *
       * @return a sorted list of navigation elements with the navigation tree level property set
       *
       * @see #getNavigationTreeForFolder(String, int, int)
       */
      public List<CmsJspNavElement> getNavigationTreeForFolder(int startlevel, int endlevel) {
  
          return getNavigationTreeForFolder(m_requestUriFolder, startlevel, endlevel);
      }
  
      /**
       * Builds a tree navigation for the folders between the provided start and end level.<p>
       *
       * @param folder the selected folder
       * @param startlevel the start level
       * @param endlevel the end level
       *
       * @return a sorted list of navigation elements with the navigation tree level property set
       */
      public List<CmsJspNavElement> getNavigationTreeForFolder(String folder, int startlevel, int endlevel) {
  
          folder = CmsResource.getFolderPath(folder);
          // Make sure start and end level make sense
          if (endlevel < startlevel) {
              return Collections.<CmsJspNavElement> emptyList();
          }
          int currentlevel = CmsResource.getPathLevel(folder);
          if (currentlevel < endlevel) {
              endlevel = currentlevel;
          }
          if (startlevel == endlevel) {
              return getNavigationForFolder(CmsResource.getPathPart(folder, startlevel), startlevel);
          }
  
          List<CmsJspNavElement> result = new ArrayList<CmsJspNavElement>();
          float parentcount = 0;
  
          for (int i = startlevel; i <= endlevel; i++) {
              String currentfolder = CmsResource.getPathPart(folder, i);
              List<CmsJspNavElement> entries = getNavigationForFolder(currentfolder);
              // Check for parent folder
              if (parentcount > 0) {
                  for (CmsJspNavElement e : entries) {
                      e.setNavPosition(e.getNavPosition() + parentcount);
                  }
              }
              // Add new entries to result
              result.addAll(entries);
              Collections.sort(result);
              // Finally spread the values of the navigation items so that there is enough room for further items
              float pos = 0;
              int count = 0;
              String nextfolder = CmsResource.getPathPart(folder, i + 1);
              parentcount = 0;
              for (CmsJspNavElement e : result) {
                  pos = 10000 * (++count);
                  e.setNavPosition(pos);
                  if (e.getResourceName().startsWith(nextfolder)) {
                      parentcount = pos;
                  }
              }
              if (parentcount == 0) {
                  parentcount = pos;
              }
          }
          return result;
      }
  
      /**
       * This method builds a complete site navigation tree with entries of all branches.<p>
       *
       * @see #getSiteNavigation(String, int)
       *
       * @return list of navigation elements, in depth first order
       */
      public List<CmsJspNavElement> getSiteNavigation() {
  
          return getSiteNavigation("/", Visibility.navigation, -1);
      }
  
      /**
       * This method builds a complete navigation tree with entries of all branches
       * from the specified folder.<p>
       *
       * @param folder folder the root folder of the navigation tree
       * @param endLevel the end level of the navigation
       *
       * @return list of navigation elements, in depth first order
       */
      public List<CmsJspNavElement> getSiteNavigation(String folder, int endLevel) {
  
          return getSiteNavigation(folder, Visibility.navigation, endLevel);
      }
  
      /**
       * This method builds a complete navigation tree with entries of all branches
       * from the specified folder.<p>
       *
       * @param folder folder the root folder of the navigation tree
       * @param visibility controls whether entries hidden from navigation or not in navigation at all should be included
       * @param endLevel the end level of the navigation
       *
       * @return list of navigation elements, in depth first order
       */
      public List<CmsJspNavElement> getSiteNavigation(String folder, Visibility visibility, int endLevel) {
  
          folder = CmsFileUtil.addTrailingSeparator(folder);
          // check if a specific end level was given, if not, build the complete navigation
          boolean noLimit = false;
          if (endLevel < 0) {
              noLimit = true;
          }
          List<CmsJspNavElement> list = new ArrayList<CmsJspNavElement>();
          // get the navigation for this folder
          List<CmsJspNavElement> curnav = getNavigationForFolder(folder, visibility, CmsResourceFilter.DEFAULT);
          // loop through all navigation entries
          for (CmsJspNavElement ne : curnav) {
              // add the navigation entry to the result list
              list.add(ne);
              // 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
              if ((ne.isFolderLink() || ne.isNavigationLevel()) && (noLimit || (ne.getNavTreeLevel() < endLevel))) {
                  List<CmsJspNavElement> subnav = getSiteNavigation(
                      m_cms.getSitePath(ne.getResource()),
                      visibility,
                      endLevel);
                  // copy the result of the subfolder to the result list
                  list.addAll(subnav);
              }
          }
          return list;
      }
  
      /**
       * Initializes this bean.<p>
       *
       * @param cms the current cms context
       */
      public void init(CmsObject cms) {
  
          init(cms, null, cms.getRequestContext().getUri());
      }
  
      /**
       * Initializes this bean.<p>
       *
       * @param cms the current cms context
       * @param locale the locale for which properties should be read
       */
      public void init(CmsObject cms, Locale locale) {
  
          init(cms, locale, cms.getRequestContext().getUri());
      }
  
      /**
       * Initializes this bean.<p>
       *
       * @param cms the current cms context
       * @param locale the locale for which properties should be read
       * @param requestUri the request URI
       */
      public void init(CmsObject cms, Locale locale, String requestUri) {
  
          m_cms = cms;
          m_locale = locale;
          m_requestUri = requestUri;
          m_requestUriFolder = CmsResource.getFolderPath(m_requestUri);
      }
  
      /**
       * Collect all navigation elements from the files in the given folder.<p>
      *
      * @param folder the selected folder
      * @param includeInvisible <code>true</code> to include elements not visible in navigation
      * @param resourceFilter the filter to use reading the resources
      * @param shallow <code>true</code> for a shallow look up, not regarding next level resources
      *
      * @return A sorted (ascending to navigation position) list of navigation elements
      */
      private List<CmsJspNavElement> getNavigationForFolder(
          String folder,
          boolean includeInvisible,
          CmsResourceFilter resourceFilter,
          boolean shallow) {
  
          folder = CmsResource.getFolderPath(folder);
          List<CmsJspNavElement> result = new ArrayList<CmsJspNavElement>();
  
          List<CmsResource> resources;
          try {
              resources = m_cms.getResourcesInFolder(folder, resourceFilter);
          } catch (Exception e) {
              // should never happen
              LOG.error(e.getLocalizedMessage(), e);
              return Collections.<CmsJspNavElement> emptyList();
          }
  
          for (CmsResource r : resources) {
              CmsJspNavElement element = getNavigationForResource(m_cms.getSitePath(r), resourceFilter, shallow);
              if ((element != null) && (includeInvisible || element.isInNavigation())) {
                  result.add(element);
              }
          }
          Collections.sort(result);
          return result;
      }
  
      /**
       * Returns a navigation element for the named resource.<p>
       *
       * @param sitePath the resource name to get the navigation information for,
       *              must be a full path name, e.g. "/docs/index.html"
       * @param resourceFilter the filter to use reading the resources
       * @param shallow <code>true</code> for a shallow look up, not regarding next level resources
       *
       * @return a navigation element for the given resource
       */
      private CmsJspNavElement getNavigationForResource(
          String sitePath,
          CmsResourceFilter resourceFilter,
          boolean shallow) {
  
          CmsResource resource;
          Map<String, String> propertiesMap;
          int level = CmsResource.getPathLevel(sitePath);
          if (sitePath.endsWith("/")) {
              level--;
          }
          try {
              resource = m_cms.readResource(sitePath, resourceFilter);
              List<CmsProperty> properties = m_cms.readPropertyObjects(resource, false);
              propertiesMap = CmsProperty.toMap(properties);
              if (resource.isFolder()) {
                  if (resourceFilter.equals(CmsResourceFilter.DEFAULT)
                      && !NAVIGATION_LEVEL_FOLDER.equals(
                          propertiesMap.get(CmsPropertyDefinition.PROPERTY_DEFAULT_FILE))) {
                      try {
                          CmsResource defaultFile = m_cms.readDefaultFile(resource, resourceFilter);
                          if ((defaultFile != null)
                              && !defaultFile.isReleasedAndNotExpired(m_cms.getRequestContext().getRequestTime())) {
                              // do not show navigation entries for unreleased or expired resources
                              return null;
                          }
                      } catch (@SuppressWarnings("unused") CmsException e) {
                          // may happen if permissions are not sufficient can be ignored
                      }
                  }
                  if (!sitePath.endsWith("/")) {
                      sitePath = sitePath + "/";
                  }
                  if (!shallow
                      && (NAVIGATION_LEVEL_FOLDER.equals(
                          propertiesMap.get(CmsPropertyDefinition.PROPERTY_DEFAULT_FILE)))) {
                      // this folder is marked as a navigation level, set the site path to the first sub element
                      List<CmsJspNavElement> subElements = getNavigationForFolder(sitePath, false, resourceFilter, true);
                      if (!subElements.isEmpty()) {
                          CmsJspNavElement subElement = subElements.get(0);
                          subElement = getNavigationForResource(subElement.getSitePath(), resourceFilter, false);
                          sitePath = subElement.getSitePath();
                      }
                  }
              }
          } catch (Exception e) {
              // may happen if permissions are not sufficient
              LOG.warn(e.getLocalizedMessage(), e);
              return null;
          }
  
          return new CmsJspNavElement(sitePath, resource, propertiesMap, level, m_locale);
      }
  }
  