001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.jsp.util;
029
030import org.opencms.file.CmsObject;
031import org.opencms.file.CmsResource;
032import org.opencms.main.CmsException;
033import org.opencms.main.CmsLog;
034import org.opencms.relations.CmsCategory;
035import org.opencms.relations.CmsCategoryService;
036import org.opencms.util.CmsCollectionsGenericWrapper;
037
038import java.util.ArrayList;
039import java.util.Collection;
040import java.util.Iterator;
041import java.util.List;
042import java.util.Map;
043import java.util.regex.Pattern;
044
045import org.apache.commons.collections.Transformer;
046import org.apache.commons.logging.Log;
047
048/** Bean for easy access to categories of a resource in JSPs. */
049public class CmsJspCategoryAccessBean {
050
051    /** The log object for this class. */
052    private static final Log LOG = CmsLog.getLog(CmsJspCategoryAccessBean.class);
053
054    /** The wrapped list of categories. */
055    List<CmsCategory> m_categories;
056
057    /** The path of the main category. All categories of {@link #m_categories} are sub-categories of the main category. */
058    String m_mainCategoryPath;
059
060    /** Map from the path of a main category to wrappers that hold only the sub-categories of that main category. */
061    Map<String, CmsJspCategoryAccessBean> m_subCategories;
062
063    /** The CMS context to use. */
064    private CmsObject m_cms;
065
066    /**
067     * Default constructor.
068     *
069     * @param cms the current {@link CmsObject}.
070     * @param resource the resource for which the categories should be read.
071     */
072    public CmsJspCategoryAccessBean(CmsObject cms, CmsResource resource) {
073        this(cms, getCategories(cms, resource), "");
074    }
075
076    /**
077     * Internal constructor for creating wrappers with a subset of the categories.
078     *
079     * @param cms the CMS context to use
080     * @param categories the original categories.
081     * @param mainCategoryPath path of the main category for which only sub-categories should be wrapped.
082     */
083    CmsJspCategoryAccessBean(CmsObject cms, List<CmsCategory> categories, String mainCategoryPath) {
084        m_cms = cms;
085        m_mainCategoryPath = mainCategoryPath.isEmpty() || mainCategoryPath.endsWith("/")
086        ? mainCategoryPath
087        : mainCategoryPath + "/";
088
089        if (m_mainCategoryPath.isEmpty()) {
090            m_categories = categories;
091        } else {
092            List<CmsCategory> filteredCategories = new ArrayList<CmsCategory>();
093            for (CmsCategory category : categories) {
094                if (category.getPath().startsWith(m_mainCategoryPath)
095                    && !category.getPath().equals(m_mainCategoryPath)) {
096                    filteredCategories.add(category);
097                }
098            }
099            m_categories = filteredCategories;
100        }
101        m_categories = CmsCategoryService.getInstance().localizeCategories(
102            cms,
103            m_categories,
104            cms.getRequestContext().getLocale());
105    }
106
107    /**
108     * Reads the categories for the given resource.
109     *
110     * @param cms the {@link CmsObject} used for reading the categories.
111     * @param resource the resource for which the categories should be read.
112     * @return the categories assigned to the given resource.
113     */
114    private static List<CmsCategory> getCategories(CmsObject cms, CmsResource resource) {
115
116        if ((null != resource) && (null != cms)) {
117            try {
118                return CmsCategoryService.getInstance().readResourceCategories(cms, resource);
119            } catch (CmsException e) {
120                LOG.error(e.getLocalizedMessage(), e);
121            }
122        }
123        return new ArrayList<CmsCategory>(0);
124    }
125
126    /**
127     * Checks if the wrapped categories contain a given category.
128     *
129     * @param categoryPath the complete path of the category to check, e.g. "location/africa/"
130     * @return <code>true</code> iff the wrapped categories contain a category with the provided categoryPath.
131     */
132    public boolean contains(String categoryPath) {
133
134        if ((null != m_categories) && (categoryPath != null)) {
135            return m_categories.stream().anyMatch((c) -> categoryPath.equals(c.getPath()));
136        }
137        return false;
138    }
139
140    /**
141     * Checks if the wrapped categories contain all given categories.
142     *
143     * @param categoryPaths the complete paths of the categories to check, e.g. ["location/africa/", "news/"]
144     * @return <code>true</code> iff for each of the provided categoryPaths, the wrapped categories contain a category with this path.
145     */
146    public boolean containsAll(Collection<String> categoryPaths) {
147
148        if ((categoryPaths == null) || (categoryPaths.size() == 0)) {
149            return true;
150        }
151        return categoryPaths.stream().allMatch((path) -> this.contains(path));
152    }
153
154    /**
155     * Checks if the wrapped categories contain at lease one of the given categories.
156     *
157     * @param categoryPaths the complete paths of the categories to check, e.g. ["location/africa/", "news/"]
158     * @return <code>true</code> iff for at least one of the provided categoryPaths, the wrapped categories contain a category with this path.
159     */
160    public boolean containsAny(Collection<String> categoryPaths) {
161
162        if ((categoryPaths == null) || (categoryPaths.size() == 0)) {
163            return false;
164        }
165        return categoryPaths.stream().anyMatch((path) -> this.contains(path));
166    }
167
168    /**
169     * Returns all wrapped categories.
170     *
171     * @return all wrapped categories.
172     */
173    public List<CmsCategory> getAllItems() {
174
175        return m_categories;
176    }
177
178    /**
179     * Returns <code>true</code> if there is no category wrapped, otherwise <code>false</code>.
180     *
181     * @return <code>true</code> if there is no category wrapped, otherwise <code>false</code>.
182     */
183    public boolean getIsEmpty() {
184
185        return m_categories.isEmpty();
186    }
187
188    /**
189     * Returns only the leaf categories of the wrapped categories.
190     *
191     * The method assumes that categories are ordered in the list, i.e., parents are directly followed by their children.
192     *
193     * NOTE: In the complete category tree a leaf of the wrapped tree part may not be a leaf.
194     *
195     * @return only the leaf categories of the wrapped categories.
196     */
197    public List<CmsCategory> getLeafItems() {
198
199        List<CmsCategory> result = new ArrayList<CmsCategory>();
200        if (m_categories.isEmpty()) {
201            return result;
202        }
203        Iterator<CmsCategory> it = m_categories.iterator();
204        CmsCategory current = it.next();
205        while (it.hasNext()) {
206            CmsCategory next = it.next();
207            if (!next.getPath().startsWith(current.getPath())) {
208                result.add(current);
209            }
210            current = next;
211        }
212        result.add(current);
213        return result;
214    }
215
216    /**
217     * Returns a map from a category path to the wrapper of all the sub-categories of the category with the path given as key.
218     *
219     * @return a map from a category path to all sub-categories of the path's category.
220     */
221    public Map<String, CmsJspCategoryAccessBean> getSubCategories() {
222
223        if (m_subCategories == null) {
224            m_subCategories = CmsCollectionsGenericWrapper.createLazyMap(new Transformer() {
225
226                @SuppressWarnings("synthetic-access")
227                public Object transform(Object pathPrefix) {
228
229                    return new CmsJspCategoryAccessBean(m_cms, m_categories, (String)pathPrefix);
230                }
231
232            });
233        }
234        return m_subCategories;
235    }
236
237    /**
238     * Returns all categories that are direct children of the current main category.
239     *
240     * @return all categories that are direct children of the current main category.
241     */
242    public List<CmsCategory> getTopItems() {
243
244        List<CmsCategory> categories = new ArrayList<CmsCategory>();
245        String matcher = Pattern.quote(m_mainCategoryPath) + "[^/]*/";
246        for (CmsCategory category : m_categories) {
247            if (category.getPath().matches(matcher)) {
248                categories.add(category);
249            }
250        }
251        return categories;
252    }
253}