org.opencms.jsp.CmsJspNavBuilder Maven / Gradle / Ivy
Show all versions of opencms-core Show documentation
/*
* This library is part of OpenCms -
* the Open Source Content Management System
*
* Copyright (c) Alkacon Software GmbH & Co. KG (http://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: http://www.alkacon.com
*
* For further information about OpenCms, please see the
* project website: http://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
* {@link org.opencms.jsp.CmsJspNavElement}
.
*
* Use this together with the {@link org.opencms.jsp.CmsJspActionElement}
* to obtain navigation information based on the current users permissions.
* For example, use {@link #getNavigationForFolder(String)}
and pass the
* value of the current OpenCms user context uri obtained
* from {@link org.opencms.file.CmsRequestContext#getUri()}
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.
*
* @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.
*/
public CmsJspNavBuilder() {
// empty
}
/**
* Default constructor.
*
* @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.
*
* @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 null
if the navigation element is not a folder.
*
* The default file of a folder is determined by the value of the property
* default-file
or the system wide property setting.
*
* @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}.
*
* @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 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.
*
* If level is set to zero the root folder is used by convention.
*
* @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 getNavigationForFolder(CmsObject cms, String folder, int level) {
return new CmsJspNavBuilder(cms).getNavigationForFolder(folder, level);
}
/**
* Returns a navigation element for the named resource.
*
* @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.
*
* 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.
*
* @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 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.
*
* For an unlimited depth of the navigation (i.e. no endLevel
),
* set the endLevel
to a value < 0.
*
*
* @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 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.
*
* @param cms the cms context
* @param resource the resource
*
* @return true
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.
*
* @return ArrayList sorted list of navigation elements
*
* @see #getNavigationBreadCrumb(String, int, int, boolean)
*/
public List getNavigationBreadCrumb() {
return getNavigationBreadCrumb(m_requestUriFolder, 0, -1, true);
}
/**
* Build a "bread crumb" path navigation to the current folder.
*
* @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 getNavigationBreadCrumb(int startlevel, boolean currentFolder) {
return getNavigationBreadCrumb(m_requestUriFolder, startlevel, -1, currentFolder);
}
/**
* Build a "bread crumb" path navigation to the current 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
*
* @return sorted list of navigation elements
*
* @see #getNavigationBreadCrumb(String, int, int, boolean)
*/
public List getNavigationBreadCrumb(int startlevel, int endlevel) {
return getNavigationBreadCrumb(m_requestUriFolder, startlevel, endlevel, true);
}
/**
* Build a "bread crumb" path navigation to the given folder.
*
* The startlevel
marks the point where the navigation starts from, if negative,
* the count of steps to go down from the given folder.
*
* The endlevel
is the maximum level of the navigation path, set it to -1 to build the
* complete navigation to the given folder.
*
* You can include the given folder in the navigation by setting currentFolder
to
* true
, otherwise false
.
*
* @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 getNavigationBreadCrumb(
String folder,
int startlevel,
int endlevel,
boolean currentFolder) {
List result = new ArrayList();
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.
*
* @return a sorted (ascending to navigation position) list of navigation elements
*/
public List 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.
*
* If level is set to zero the root folder is used by convention.
*
* @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 getNavigationForFolder(int level) {
return getNavigationForFolder(m_requestUriFolder, level);
}
/**
* Collect all navigation visible elements from the files in the given folder.
*
* @param folder the selected folder
*
* @return A sorted (ascending to navigation position) list of navigation elements
*/
public List 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.
*
* If level is set to zero the root folder is used by convention.
*
* @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 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. emptyList();
}
/**
* Collect all navigation elements from the files in the given folder.
*
* @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 getNavigationForFolder(
String folder,
Visibility visibility,
CmsResourceFilter resourceFilter) {
folder = CmsFileUtil.removeTrailingSeparator(folder);
List result = new ArrayList();
List 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. 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.
*
* @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.
*
* @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.
*
* @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.
*
* @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 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.
*
* @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 getNavigationTreeForFolder(String folder, int startlevel, int endlevel) {
folder = CmsResource.getFolderPath(folder);
// Make sure start and end level make sense
if (endlevel < startlevel) {
return Collections. emptyList();
}
int currentlevel = CmsResource.getPathLevel(folder);
if (currentlevel < endlevel) {
endlevel = currentlevel;
}
if (startlevel == endlevel) {
return getNavigationForFolder(CmsResource.getPathPart(folder, startlevel), startlevel);
}
List result = new ArrayList();
float parentcount = 0;
for (int i = startlevel; i <= endlevel; i++) {
String currentfolder = CmsResource.getPathPart(folder, i);
List 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.
*
* @see #getSiteNavigation(String, int)
*
* @return list of navigation elements, in depth first order
*/
public List getSiteNavigation() {
return getSiteNavigation("/", Visibility.navigation, -1);
}
/**
* This method builds a complete navigation tree with entries of all branches
* from the specified folder.
*
* @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 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.
*
* @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 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 list = new ArrayList();
// get the navigation for this folder
List 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 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.
*
* @param cms the current cms context
*/
public void init(CmsObject cms) {
init(cms, null, cms.getRequestContext().getUri());
}
/**
* Initializes this bean.
*
* @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.
*
* @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.
*
* @param folder the selected folder
* @param includeInvisible true
to include elements not visible in navigation
* @param resourceFilter the filter to use reading the resources
* @param shallow true
for a shallow look up, not regarding next level resources
*
* @return A sorted (ascending to navigation position) list of navigation elements
*/
private List getNavigationForFolder(
String folder,
boolean includeInvisible,
CmsResourceFilter resourceFilter,
boolean shallow) {
folder = CmsResource.getFolderPath(folder);
List result = new ArrayList();
List resources;
try {
resources = m_cms.getResourcesInFolder(folder, resourceFilter);
} catch (Exception e) {
// should never happen
LOG.error(e.getLocalizedMessage(), e);
return Collections. 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.
*
* @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 true
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 propertiesMap;
int level = CmsResource.getPathLevel(sitePath);
if (sitePath.endsWith("/")) {
level--;
}
try {
resource = m_cms.readResource(sitePath, resourceFilter);
List 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 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);
}
}