/* * File : $Source: /alkacon/cvs/alkacon/com.alkacon.opencms.documentcenter/src/com/alkacon/opencms/documentcenter/CategoryTree.java,v $ * Date : $Date: 2010/03/19 15:31:14 $ * Version: $Revision: 1.3 $ * * This file is part of the Alkacon OpenCms Add-On Module Package * * Copyright (c) 2010 Alkacon Software GmbH (http://www.alkacon.com) * * The Alkacon OpenCms Add-On Module Package is free software: * you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * The Alkacon OpenCms Add-On Module Package 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with the Alkacon OpenCms Add-On Module Package. * If not, see http://www.gnu.org/licenses/. * * For further information about Alkacon Software GmbH, please see the * company website: http://www.alkacon.com. * * For further information about OpenCms, please see the * project website: http://www.opencms.org. */ package com.alkacon.opencms.v8.documentcenter; import org.opencms.file.CmsFolder; import org.opencms.file.CmsObject; import org.opencms.file.CmsPropertyDefinition; import org.opencms.file.CmsResource; import org.opencms.file.CmsResourceFilter; import org.opencms.file.CmsUser; import org.opencms.jsp.CmsJspNavBuilder; import org.opencms.jsp.CmsJspNavElement; import org.opencms.main.CmsException; import org.opencms.main.CmsLog; import org.opencms.util.CmsFileUtil; import org.opencms.util.CmsStringUtil; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.StringTokenizer; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import org.apache.commons.logging.Log; /** * A Java bean to do a query for VFS documents.<p> * * The category tree is internally stored by a map of adjacency lists * containing category objects keyed by their parent categories/folders.<p> * * @author Thomas Weckert * * @version $Revision: 1.3 $ * * @since 6.0.0 */ public class CategoryTree { /** The form action to trigger a search for new documents. */ public static final String C_ACTION_SEARCH = "search"; /** The form action to toggle the category tree. */ public static final String C_ACTION_TOGGLE_TREE = "toggleTree"; /** The default max. tree depth of the category tree. */ public static final int C_DEFAULT_MAX_TREE_DEPTH = 2; /** The sep.-char. to build lists. */ public static final String C_LIST_SEPARATOR = ","; /** The category toggle +/- in the tree. */ public static final String C_PARAM_TOGGLE_CATEGORY = "toggleCategory"; /** The mode +/- how to tree was toggled. */ public static final String C_PARAM_TOGGLE_MODE = "toggleMode"; /** The category property. */ public static final String C_PROP_CATEGORY = "category"; /** The property key to read the max. folder/category depth of the tree. */ public static final String C_PROPERTY_MAX_TREE_DEPTH = "categoryTreeMaxDepth"; /** The key to get the opened categories from the user info hash. */ public static final String C_USER_INFO_OPENED_CATEGORIES = "opened_categories"; /** The key to get the selected categories from the user info hash. */ public static final String C_USER_INFO_SELECTED_CATEGORIES = "selected_categories"; /** A separator for property prefixes. */ private static final String C_PREFIX_SEPARATOR = ":"; /** The log object for this class. */ private static final Log LOG = CmsLog.getLog(CategoryTree.class); /** The action submitted from the form what to do next. */ private String m_action; /** A boolean flag that saves if the user checked the "select all categories" checkbox in the form. */ private boolean m_allSelected; /** The current user's Cms object. */ private CmsObject m_cms; /** The max. folder/category depth of the tree. */ private int m_maxTreeDepth; /** The folder names of the categories opened in the tree. */ private List<String> m_openedCategoryList; /** The name of the property definition for the localized title, e.g. "Title_en". */ private String m_propertyTitle; /** The request used to initialize the Category Tree. */ private HttpServletRequest m_request; /** The root folder from where the tree is built. */ private String m_rootFolder; /** The folder names of the categories selected in the tree. */ private List<String> m_selectedCategoryList; /** The map to stroe the category tree. */ private Map<String, List<CmsCategory>> m_treeMap; /** Indicates if navigation properties should be used to create the category tree. */ private boolean m_useNavigation; /** * Creates a new category tree.<p> * @param cms the current user's Cms object * @param request the Http request * @param rootFolder the root folder from where the category tree is built (usually "/") * @param maxTreeDepth the max. category/folder depth of the tree */ public CategoryTree(CmsObject cms, HttpServletRequest request, String rootFolder, String maxTreeDepth) { String value = null; String mode = null; String category = null; m_cms = cms; m_rootFolder = (rootFolder != null) ? CmsFileUtil.addTrailingSeparator(rootFolder) : rootFolder; if ((m_rootFolder != null) && (m_rootFolder.length() > 1)) { // check if navigation properties should be used for category tree List<CmsResource> allCategories = new ArrayList<CmsResource>(); try { allCategories = cms.readResourcesWithProperty( m_rootFolder, C_PROP_CATEGORY, null, CmsResourceFilter.DEFAULT); } catch (CmsException e) { // should never happen } m_useNavigation = allCategories.size() < 1; } m_request = request; m_propertyTitle = CmsPropertyDefinition.PROPERTY_TITLE + "_" + cms.getRequestContext().getLocale().toString(); if (maxTreeDepth != null) { try { m_maxTreeDepth = Integer.parseInt(maxTreeDepth); } catch (Exception e) { if (LOG.isErrorEnabled()) { LOG.error( "Error: maxTreeDepth is not a string representing an integer! Using default max. tree depth " + C_DEFAULT_MAX_TREE_DEPTH, e); } m_maxTreeDepth = C_DEFAULT_MAX_TREE_DEPTH; } } else { m_maxTreeDepth = C_DEFAULT_MAX_TREE_DEPTH; if (LOG.isErrorEnabled()) { LOG.error("Error: maxTreeDepth is null! Using default max. tree depth " + C_DEFAULT_MAX_TREE_DEPTH); } } // save all required request params. value = request.getParameter("action"); if (value != null) { m_action = value; } value = request.getParameter("openedCategories"); if (value != null) { m_openedCategoryList = commaStringToList(value, C_LIST_SEPARATOR); } else { m_openedCategoryList = new ArrayList<String>(); } value = request.getParameter("categorylist"); if (value != null) { m_selectedCategoryList = commaStringToList(value, C_LIST_SEPARATOR); } else { m_selectedCategoryList = new ArrayList<String>(); } value = request.getParameter("all"); if (value != null) { m_allSelected = true; } else { m_allSelected = false; } if (LOG.isDebugEnabled()) { LOG.debug("################ INPUT VALUES FOR CATEGORY TREE"); LOG.debug("action : " + m_action); LOG.debug("opened categories : " + m_openedCategoryList.toString()); LOG.debug("selected categories : " + m_selectedCategoryList.toString()); LOG.debug("root folder : " + m_rootFolder); LOG.debug("max. tree depth : " + m_maxTreeDepth); } // initialize everything by switching the submitted form action if (m_action != null) { if (C_ACTION_TOGGLE_TREE.equalsIgnoreCase(m_action)) { // the user toggled the tree... mode = request.getParameter(C_PARAM_TOGGLE_MODE); category = request.getParameter(C_PARAM_TOGGLE_CATEGORY); if ((mode != null) && (category != null)) { if ("+".equalsIgnoreCase(mode)) { // the user opened a sub-tree openCategory(category); saveTreeInfo(); } else if ("-".equalsIgnoreCase(mode)) { // the user closed a sub-tree closeCategory(category); saveTreeInfo(); } else if (LOG.isErrorEnabled()) { LOG.debug("Unknown toggle mode " + mode + " submitted!"); } } } else if ("redirect_a".equalsIgnoreCase(m_action) || C_ACTION_SEARCH.equalsIgnoreCase(m_action) || ("searchText".equalsIgnoreCase(m_action))) { // the user triggered a search for new documents... saveTreeInfo(); } } } /** * Turns comma-separated string into a List of strings.<p> * * @param str the string to be split * @param sep a separator character * @return a List of tokens */ public static List<String> commaStringToList(String str, String sep) { List<String> result = null; StringTokenizer tokenizer = null; if (str != null) { tokenizer = new StringTokenizer(str, sep); result = new ArrayList<String>(tokenizer.countTokens()); while (tokenizer.hasMoreTokens()) { result.add(tokenizer.nextToken()); } Collections.sort(result); } else { result = new ArrayList<String>(); } return result; } /** * Cuts off a prefix separated by <code>:</code> from the "real" property value.<p> * * @param value a property value * @return the property value without the prefix */ public static String cutPrefix(String value) { if (value == null) { return value; } int index = value.indexOf(C_PREFIX_SEPARATOR); if (index != -1) { return value.substring(index + 1); } return value; } /** * Returns the tree info for the specified key from the current user's info hash.<p> * * @param cms the CmsObject * @param key the key * @param request the current request * * @return the tree info for the specified key * @see #C_USER_INFO_OPENED_CATEGORIES * @see #C_USER_INFO_SELECTED_CATEGORIES */ public static String getTreeInfo(CmsObject cms, String key, HttpServletRequest request) { key = cms.getRequestContext().addSiteRoot(cms.getRequestContext().getUri()) + "_" + key; // try to read the tree settings fomr the request String value; value = (String)request.getSession(true).getAttribute(key); // if nothing was found in the session, try to read some infoes from the user if (value == null) { value = (String)cms.getRequestContext().getCurrentUser().getAdditionalInfo(key); } if (LOG.isDebugEnabled()) { LOG.debug("retrieving additional info for user " + cms.getRequestContext().getCurrentUser().getName() + " with key: " + key + ", value: " + value); } return value; } /** * Turns a List of strings into a comma-separated string.<p> * * @param list the list * @param sep the separator * @return a comma-separated string */ public static String listToCommaString(List<String> list, String sep) { StringBuffer buf = new StringBuffer(); for (int i = 0, n = list.size(); i < n; i++) { buf.append(list.get(i)); if (i < (n - 1)) { buf.append(sep); } } return buf.toString(); } /** * Builds the HTML of the category tree.<p> * @param categoryTree a List with the categories currently visible in the tree * @param optCheckboxArgs optional arguments to be included in the checkbox tags, or null * @param optAnchorArgs optional arguments to be included in the anchor tags to toggle the tree, or null * @param indent a HTML fragment (e.g. a span tag) to indent sub-elements in the tree * @return the HTML of the category tree */ public String buildCategoryTree( List<CmsCategory> categoryTree, String optCheckboxArgs, String optAnchorArgs, String indent) { StringBuffer buf = new StringBuffer(); CmsCategory category = null; String resourceName = null; String toggleMode = null; int counter = 0; int pathLevel = 0; int pathLevelRootFolder = CmsResource.getPathLevel(m_rootFolder) + 1; // build the HTML of the category tree for (int i = 0, n = categoryTree.size(); i < n; i++) { category = categoryTree.get(i); // check if this is a main category boolean mainCat = category.isMainCategory(); resourceName = category.getCmsResource(); if ((resourceName != null) && !"".equalsIgnoreCase(resourceName)) { toggleMode = m_openedCategoryList.contains(category.getCmsResource()) ? "-" : "+"; pathLevel = CmsResource.getPathLevel(category.getCmsResource()) - pathLevelRootFolder; for (int j = 0; j < pathLevel; j++) { buf.append(indent); } buf.append("<input type=\"checkbox\" name=\"cat").append(counter).append("\""); buf.append(" id=\"cat").append(counter).append("\""); buf.append(" value=\"").append(resourceName).append("\""); if (m_selectedCategoryList.contains(resourceName)) { buf.append(" checked=\"checked\""); } if (optCheckboxArgs != null) { buf.append(" ").append(optCheckboxArgs); } buf.append("/> "); buf.append("<a"); if (optAnchorArgs != null) { buf.append(" ").append(optAnchorArgs); } buf.append( " href=\"#\" class=\"btn btn-info btn-small\" onclick=\"toggleTree(" + categoryTree.size() + ",'" + resourceName + "','").append(toggleMode).append("')\">"); buf.append("[").append(toggleMode).append("]</a>"); buf.append(" "); if (mainCat) { buf.append("<span class=\"treetopcategory\">"); } buf.append("<label for=\"").append("cat").append(counter).append("\">"); buf.append(category.getTitle()); buf.append("</label>"); if (mainCat) { buf.append("</span>"); } buf.append("<br/>\n"); counter++; } } return buf.toString(); } /** * Returns a list of all categories sorted ascending by their position in the * category tree.<p> * * The tree contains the main-categories, plus the sub-categories of all opened * categories.<p> * * @return a list of all categories */ public List<CmsCategory> getCategoryTree() { List<CmsCategory> result = null; CmsCategory category = null; String parentFolder = null; List<CmsCategory> treeList = null; boolean addToResult = false; try { // create a new tree map m_treeMap = Collections.synchronizedMap(new HashMap<String, List<CmsCategory>>()); // add all selected categories to the tree addSelectedCategories(); // add all opened categories to the tree addOpenedCategories(); // add all folders having the "category" property set to tree addCategoryFolders(); // turn the tree map into a List in DFS order treeList = toList(new CmsCategory("", "", m_rootFolder, true)); if (treeList == null) { // the tree is empty... return Collections.emptyList(); } else { // tree[0] is the root folder and can be skipped from the result tree if (treeList.size() > 1) { treeList = treeList.subList(1, treeList.size()); } } // build the final result tree. this is to include only sub-trees // in the result that either have a parent folder being opened, // or a "sibling" node in the tree that is selected result = new ArrayList<CmsCategory>(); for (int i = 0, n = treeList.size(); i < n; i++) { category = treeList.get(i); parentFolder = CmsResource.getParentFolder(category.getCmsResource()); // add the category to the result if it is a main category addToResult = category.isMainCategory(); // or if it is a sub-folder/category of an opened category addToResult |= m_openedCategoryList.contains(parentFolder); // and if it's folder depth is below the max. folder depth addToResult &= CmsResource.getPathLevel(category.getCmsResource()) <= m_maxTreeDepth; if (addToResult) { result.add(category); } } } catch (Exception e) { if (LOG.isErrorEnabled()) { LOG.error("Error building category tree", e); } } return result; } /** * Returns the comma-separated string of opened categories.<p> * * @return the comma-separated string of opened categories */ public String getOpenedCategories() { if ((m_openedCategoryList == null) || (m_openedCategoryList.size() == 0)) { return ""; } return listToCommaString(m_openedCategoryList, C_LIST_SEPARATOR); } /** * Returns "true" if all categories should be pre-selected in the form.<p> * * This string can be used to trigger a JavaScript in the form.<p> * * @return "true" if all categories should be pre-selected in the form */ public String getPreSelectAllCategories() { // all categories should be pre-selected either if the user checked the // "select all categories" checkbox", or if the user openes the form for // the first time "action=null" and he doesn't have a tree of selected // categories saved in his user info hash. if (m_allSelected || (((m_action == null) || "".equalsIgnoreCase(m_action)) && (m_selectedCategoryList.size() == 0))) { return "true"; } return "false"; } /** * Returns the root folder from where the tree is built.<p> * * @return the root folder from where the tree is built */ public String getRootFolder() { return m_rootFolder; } /** * Returns the comma-separated string of selected categories.<p> * * @return the comma-separated string of selected categories */ public String getSelectedCategories() { if ((m_selectedCategoryList == null) || (m_selectedCategoryList.size() == 0)) { return ""; } return listToCommaString(m_selectedCategoryList, C_LIST_SEPARATOR); } /** * Adds a category to the tree if it is not already contained in the tree.<p> * * @param parent the parent folder of the category * @param category the category */ protected void addCategory(String parent, CmsCategory category) { // get the child list of the folder specified by the parent ID List<CmsCategory> children = m_treeMap.get(parent); if (children == null) { // the child is obviously the first child of it's parent folder children = new ArrayList<CmsCategory>(); // add the new child list as a sub-tree m_treeMap.put(parent, children); } if (!children.contains(category)) { // add the child to the child list children.add(category); } } /** * Adds all folders having the "category" property set to the tree .<p> */ protected void addCategoryFolders() { List<CmsResource> resources = null; CmsResource resource = null; String position = null; String title = null; String resourceName = null; CmsCategory category = null; String parentFolder = null; if (m_useNavigation) { CmsJspNavBuilder navBuilder = new CmsJspNavBuilder(m_cms); CmsJspNavElement rootElement = navBuilder.getNavigationForResource(m_rootFolder); List<CmsJspNavElement> navElements = navBuilder.getSiteNavigation( m_rootFolder, rootElement.getNavTreeLevel() + m_maxTreeDepth); resources = new ArrayList<CmsResource>(navElements.size()); for (CmsJspNavElement navItem : navElements) { if (navItem.isFolderLink()) { resources.add(navItem.getResource()); } } } else { try { // read all resources having the "category" property set from current root folder resources = m_cms.readResourcesWithProperty(m_rootFolder, C_PROP_CATEGORY); } catch (CmsException e) { if (LOG.isErrorEnabled()) { LOG.error("Error reading resources with property " + C_PROP_CATEGORY, e); } resources = Collections.emptyList(); } } // turn the resources into a map of Category objects // keyed by their parent folder in the category tree for (int i = 0, n = resources.size(); i < n; i++) { resource = resources.get(i); try { resourceName = m_cms.getRequestContext().removeSiteRoot(resource.getRootPath()); if (m_useNavigation) { position = m_cms.readPropertyObject(resourceName, CmsPropertyDefinition.PROPERTY_NAVPOS, false).getValue(); } else { position = m_cms.readPropertyObject(resourceName, C_PROP_CATEGORY, false).getValue(); } if ((position != null) && !"".equalsIgnoreCase(position)) { position = CategoryTree.cutPrefix(position); title = readTitle(resourceName); parentFolder = CmsResource.getParentFolder(resourceName); category = new CmsCategory(title, position, resourceName, m_rootFolder.equals(parentFolder)); if (parentFolder.startsWith(m_rootFolder)) { // add only folders starting with the root folder // because we want a tree beginning from the root folder addCategory(parentFolder, category); } } } catch (CmsException e) { if (LOG.isWarnEnabled()) { LOG.warn("Error reading properties of resource " + resource.getRootPath(), e); } else if (LOG.isErrorEnabled()) { LOG.error("Error reading properties of resource " + resource.getRootPath()); } } } } /** * Adds all currently opened categories and their sub-categories/folders to the tree.<p> */ protected void addOpenedCategories() { String folder = null; String subFolder = null; String parentFolder = null; String position = null; String title = null; CmsCategory category = null; CmsCategory subCategory = null; List<String> subCategories = null; String openedCategories = null; // check if the user has a list of opened categories saved in his info hash openedCategories = getTreeInfo(m_cms, C_USER_INFO_OPENED_CATEGORIES, m_request); if ((openedCategories != null) && !"".equalsIgnoreCase(openedCategories)) { m_openedCategoryList = commaStringToList(openedCategories, C_LIST_SEPARATOR); } else { m_openedCategoryList = new ArrayList<String>(); } for (int i = 0, n = m_openedCategoryList.size(); i < n; i++) { try { folder = m_openedCategoryList.get(i); parentFolder = CmsResource.getParentFolder(folder); if (m_useNavigation) { position = m_cms.readPropertyObject(folder, CmsPropertyDefinition.PROPERTY_NAVPOS, false).getValue(); } else { position = m_cms.readPropertyObject(folder, C_PROP_CATEGORY, false).getValue(); } if ((position != null) && !"".equalsIgnoreCase(position)) { position = CategoryTree.cutPrefix(position); title = readTitle(folder); boolean isMain = (CmsResource.getPathLevel(folder) - CmsResource.getPathLevel(m_rootFolder)) == 1; category = new CmsCategory(title, position, folder, isMain); if (folder.startsWith(m_rootFolder) && !m_rootFolder.equalsIgnoreCase(folder)) { // add the opened category itself to the tree addCategory(parentFolder, category); // add all sub-categories/folders to the tree subCategories = getSubCategories(folder); for (int j = 0, m = subCategories.size(); j < m; j++) { subFolder = subCategories.get(j); if (m_useNavigation) { position = m_cms.readPropertyObject( subFolder, CmsPropertyDefinition.PROPERTY_NAVPOS, false).getValue(); } else { position = m_cms.readPropertyObject(subFolder, C_PROP_CATEGORY, false).getValue(); } if ((position != null) && !"".equalsIgnoreCase(position)) { position = CategoryTree.cutPrefix(position); title = readTitle(subFolder); subCategory = new CmsCategory(title, position, subFolder, false); addCategory(folder, subCategory); } } } } } catch (CmsException e) { if (LOG.isErrorEnabled()) { LOG.error("Error reading properties of resource " + folder, e); } } } } /** * Adds all selected categories/folders and their parent categories/folders to the tree.<p> */ protected void addSelectedCategories() { String selectedCategories = null; String folder = null; List<String> subFolders = null; String subFolder = null; String title = null; String position = null; CmsCategory category = null; // check if the user has a list of selected categories saved in his info hash selectedCategories = getTreeInfo(m_cms, C_USER_INFO_SELECTED_CATEGORIES, m_request); if ((selectedCategories != null) && !"".equalsIgnoreCase(selectedCategories)) { m_selectedCategoryList = commaStringToList(selectedCategories, C_LIST_SEPARATOR); } else { m_selectedCategoryList = new ArrayList<String>(); } // add all parent categories/folders of the selected categories to the tree for (int i = 0, n = m_selectedCategoryList.size(); i < n; i++) { folder = m_selectedCategoryList.get(i); if (!folder.startsWith(m_rootFolder)) { continue; } while (!m_rootFolder.equalsIgnoreCase(folder)) { folder = CmsResource.getParentFolder(folder); if (m_rootFolder.equalsIgnoreCase(folder)) { // continue if the parent folder is the root folder // because we want a tree beginning from the root folder continue; } boolean isMain = (CmsResource.getPathLevel(folder) - CmsResource.getPathLevel(m_rootFolder)) == 1; try { // add all sub folders of the current folder to the tree map subFolders = getSubCategories(folder); for (int j = 0, m = subFolders.size(); j < m; j++) { subFolder = subFolders.get(j); if (m_useNavigation) { position = m_cms.readPropertyObject(subFolder, CmsPropertyDefinition.PROPERTY_NAVPOS, false).getValue(); } else { position = m_cms.readPropertyObject(subFolder, C_PROP_CATEGORY, false).getValue(); } if ((position != null) && !"".equalsIgnoreCase(position)) { position = CategoryTree.cutPrefix(position); title = readTitle(subFolder); category = new CmsCategory(title, position, subFolder, isMain); addCategory(folder, category); } } } catch (CmsException e) { if (LOG.isErrorEnabled()) { LOG.error("Error reading sub-folders of " + folder, e); } } } } } /** * Removes all categories from the tree.<p> */ protected synchronized void clear() { List<CmsCategory> children = null; String parent = null; List<String> list = null; if (m_treeMap == null) { return; } // iterate over all adjacency lists to clear them list = new ArrayList<String>(m_treeMap.keySet()); for (int i = 0, n = list.size(); i < n; i++) { parent = list.get(i); // clear the adjacency list of the current parent children = m_treeMap.get(parent); children.clear(); // remove the parent from the tree m_treeMap.remove(parent); } // clear the tree map again (robustness) m_treeMap.clear(); } /** * Closes a category in the tree.<p> * * @param closedCategoryFolder the category to be closed */ protected void closeCategory(String closedCategoryFolder) { String category = null; List<String> tmpList = null; // update the list of opened categories tmpList = new ArrayList<String>(); for (int i = 0, n = m_openedCategoryList.size(); i < n; i++) { category = m_openedCategoryList.get(i); if (!category.startsWith(closedCategoryFolder)) { tmpList.add(category); } } m_openedCategoryList = tmpList; // update the list of selected categories tmpList = new ArrayList<String>(); for (int i = 0, n = m_selectedCategoryList.size(); i < n; i++) { category = m_selectedCategoryList.get(i); if (closedCategoryFolder.equalsIgnoreCase(category) || !category.startsWith(closedCategoryFolder)) { tmpList.add(category); } } m_selectedCategoryList = tmpList; } /** * @see java.lang.Object#finalize() */ @Override protected void finalize() throws Throwable { try { clear(); m_treeMap = null; } catch (Throwable t) { // ignore } super.finalize(); } /** * Returns all sub-categories/folders of a specified category.<p> * * Default case: the first 2 folder levels are categories. Thus, for the first 2 folder * levels only folders having the "category" property set should be visible in the * tree. Down from the 2nd folder level, all folders should be visible in the tree.<p> * * Overwrite this method if you want to make folders visible in the tree from * a folder level different than the default case here.<p> * * @param parentFolder the parent category folder * @return a List of all sub-categories/folders */ protected List<String> getSubCategories(String parentFolder) { String title = null; String position = null; List<CmsResource> list = null; String subFolder = null; List<String> result = new ArrayList<String>(); try { // turn the parent folder into a category if (m_useNavigation) { position = m_cms.readPropertyObject(parentFolder, CmsPropertyDefinition.PROPERTY_NAVPOS, false).getValue(); } else { position = m_cms.readPropertyObject(parentFolder, C_PROP_CATEGORY, false).getValue(); } position = CategoryTree.cutPrefix(position); title = m_cms.readPropertyObject(parentFolder, CmsPropertyDefinition.PROPERTY_TITLE, false).getValue(); if (CmsStringUtil.isEmptyOrWhitespaceOnly(title)) { // fall back: the navigation text title = m_cms.readPropertyObject(parentFolder, CmsPropertyDefinition.PROPERTY_NAVTEXT, false).getValue(); } boolean isMain = (CmsResource.getPathLevel(parentFolder) - CmsResource.getPathLevel(m_rootFolder)) == 1; if (m_useNavigation) { CmsJspNavBuilder navBuilder = new CmsJspNavBuilder(m_cms); List<CmsJspNavElement> navElements = navBuilder.getNavigationForFolder(parentFolder); list = new ArrayList<CmsResource>(navElements.size()); for (CmsJspNavElement navItem : navElements) { if (navItem.isFolderLink()) { list.add(navItem.getResource()); } } } else { list = m_cms.getSubFolders(parentFolder); } if (isMain) { // add only folders having the "category" property set if the // parent folder is a main category in the tree for (int i = 0, n = list.size(); i < n; i++) { subFolder = m_cms.getRequestContext().removeSiteRoot((list.get(i)).getRootPath()); if (m_useNavigation) { position = m_cms.readPropertyObject(subFolder, CmsPropertyDefinition.PROPERTY_NAVPOS, false).getValue(); } else { position = m_cms.readPropertyObject(subFolder, C_PROP_CATEGORY, false).getValue(); } if ((position != null) && !"".equalsIgnoreCase(position)) { position = CategoryTree.cutPrefix(position); result.add(subFolder); } } } else { for (int i = 0, n = list.size(); i < n; i++) { subFolder = m_cms.getRequestContext().removeSiteRoot(((CmsFolder)list.get(i)).getRootPath()); result.add(subFolder); } } } catch (CmsException e) { if (LOG.isErrorEnabled()) { LOG.error("Error reading properties", e); } } return result; } /** * Opens a category in the tree.<p> * * @param openedCategoryFolder the category to be opened */ protected void openCategory(String openedCategoryFolder) { List<String> subCategories = null; // the user opened a sub-tree if (!m_openedCategoryList.contains(openedCategoryFolder)) { m_openedCategoryList.add(openedCategoryFolder); if (m_selectedCategoryList.contains(openedCategoryFolder)) { // add all sub-categories of the opened category // if the opened category is currently selected subCategories = getSubCategories(openedCategoryFolder); m_selectedCategoryList.addAll(subCategories); } } } /** * Reads the value of the title property of the specified resource.<p> * * First reads the localized title, if this is not found, the common title.<p> * * @param resourceName the resource name to look up the property * @return the value of the title property of the specified resource */ protected String readTitle(String resourceName) { String title = ""; try { try { // first read the title property for the current locale title = m_cms.readPropertyObject(resourceName, m_propertyTitle, false).getValue(); } catch (CmsException exc) { // ignore, property might not exist } if (CmsStringUtil.isEmptyOrWhitespaceOnly(title)) { // localized title not found, read the usual title title = m_cms.readPropertyObject(resourceName, CmsPropertyDefinition.PROPERTY_TITLE, false).getValue(); } if (CmsStringUtil.isEmptyOrWhitespaceOnly(title)) { // usual title not found, read navigation text title = m_cms.readPropertyObject(resourceName, CmsPropertyDefinition.PROPERTY_NAVTEXT, false).getValue(); } } catch (CmsException e) { // ignore } if (CmsStringUtil.isEmptyOrWhitespaceOnly(title)) { // no title property found at all, use the resource name as title title = CmsResource.getName(resourceName); } return title; } /** * Saves the tree info (opened/selected categories) in the current user's info hash.<p> */ protected void saveTreeInfo() { String prefix = m_cms.getRequestContext().addSiteRoot(m_cms.getRequestContext().getUri()) + "_"; // save the categories that the user selected in his info hash saveUserInfo( prefix + C_USER_INFO_SELECTED_CATEGORIES, listToCommaString(m_selectedCategoryList, C_LIST_SEPARATOR)); // save the categories that the user selected in his info hash saveUserInfo(prefix + C_USER_INFO_OPENED_CATEGORIES, listToCommaString(m_openedCategoryList, C_LIST_SEPARATOR)); } /** * Saves a key/value pair in the additional info hash of the current user.<p> * * @param key the key * @param value the value */ protected void saveUserInfo(String key, String value) { CmsUser user = null; if (LOG.isDebugEnabled()) { LOG.debug("saving additional info for user " + m_cms.getRequestContext().getCurrentUser().getName() + " with key: " + key + ", value: " + value); } if (value == null) { return; } try { // store the current information about the tree stauts in the session HttpSession session = m_request.getSession(true); session.setAttribute(key, value); // if the user is not the guest user, store the settings in the user, too user = m_cms.getRequestContext().getCurrentUser(); if (!user.isGuestUser()) { user.setAdditionalInfo(key, value); m_cms.writeUser(user); } } catch (CmsException e) { if (LOG.isErrorEnabled()) { LOG.error("Error saving additional info for user " + user.getName() + " with key: " + key + ", value: " + value, e); } } } /** * Recursively builds a DFS list of a sub-tree beginning * from the specified parent category.<p> * * @param parent the category from where the sub-tree is built * @return a List of categories in the sub tree in DFS order */ protected List<CmsCategory> toList(CmsCategory parent) { List<CmsCategory> result = null; List<CmsCategory> children = null; CmsCategory category = null; if (parent == null) { return null; } result = new ArrayList<CmsCategory>(); result.add(parent); // get the adjacency list with the child objects of the current parent ID children = m_treeMap.get(parent.getCmsResource()); if (children == null) { return result; } // add the sub-tree of the current parent resource to the result list Collections.sort(children); for (int i = 0, n = children.size(); i < n; i++) { category = children.get(i); result.addAll(toList(category)); } return result; } }