001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software GmbH, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.jsp;
029
030import org.opencms.ade.sitemap.shared.CmsClientSitemapEntry;
031import org.opencms.file.CmsPropertyDefinition;
032import org.opencms.file.CmsResource;
033import org.opencms.i18n.CmsMessages;
034
035import java.util.Map;
036
037/**
038 * Bean to collect navigation information from a resource in the OpenCms VFS.<p>
039 *
040 * Each navigation element contains a number of information about a VFS resource,
041 * obtained either from the resources properties or attributes.
042 * You can use this information to generate a HTML navigation for
043 * files in the VFS in your template.<p>
044 *
045 * Note: this class has a natural ordering that is inconsistent with equals.<p>
046 *
047 * @since 6.0.0
048 *
049 * @see org.opencms.jsp.CmsJspNavBuilder
050 */
051public class CmsJspNavElement implements Comparable<CmsJspNavElement> {
052
053    /** The navigation position has changed flag. */
054    private boolean m_changedNavPos;
055
056    /** The file name. */
057    private String m_fileName;
058
059    /** The has navigation flag. */
060    private Boolean m_hasNav;
061
062    /** Flag indicating whether this is a hidden navigation entry. */
063    private Boolean m_isHiddenNavigationEntry;
064
065    /** The navigation tree level. */
066    private int m_navTreeLevel = Integer.MIN_VALUE;
067
068    /** The navigation position. */
069    private float m_position;
070
071    /** The properties. */
072    private Map<String, String> m_properties;
073
074    /** The resource. */
075    private CmsResource m_resource;
076
077    /** The site path. */
078    private String m_sitePath;
079
080    /** The navigation text. */
081    private String m_text;
082
083    /**
084     * Empty constructor required for every JavaBean, does nothing.<p>
085     *
086     * Call one of the init methods after you have created an instance
087     * of the bean. Instead of using the constructor you should use
088     * the static factory methods provided by this class to create
089     * navigation beans that are properly initialized with current
090     * OpenCms context.<p>
091     *
092     * @see CmsJspNavBuilder#getNavigationForResource()
093     * @see CmsJspNavBuilder#getNavigationForFolder()
094     * @see CmsJspNavBuilder#getNavigationTreeForFolder(int, int)
095     */
096    public CmsJspNavElement() {
097
098        // empty
099    }
100
101    /**
102     * Create a new instance of the bean and calls the init method
103     * with the provided parameters.<p>
104     *
105     * @param sitePath will be passed to <code>init</code>
106     * @param resource the resource
107     * @param properties will be passed to <code>init</code>
108     */
109    public CmsJspNavElement(String sitePath, CmsResource resource, Map<String, String> properties) {
110
111        setResource(resource);
112        init(sitePath, properties);
113    }
114
115    /**
116     * Create a new instance of the bean and calls the init method
117     * with the provided parameters.<p>
118     *
119     * @param sitePath will be passed to <code>init</code>
120     * @param resource the resource
121     * @param properties will be passed to <code>init</code>
122     * @param navTreeLevel will be passed to <code>init</code>
123     *
124     * @see #init(String, Map, int)
125     */
126    public CmsJspNavElement(String sitePath, CmsResource resource, Map<String, String> properties, int navTreeLevel) {
127
128        setResource(resource);
129        init(sitePath, properties, navTreeLevel);
130    }
131
132    /**
133     * Create a new instance of the bean and calls the init method
134     * with the provided parameters.<p>
135     *
136     * @param sitePath will be passed to <code>init</code>
137     * @param properties will be passed to <code>init</code>
138     *
139     * @see #init(String, Map)
140     *
141     * @deprecated use {@link #CmsJspNavElement(String, CmsResource, Map)}
142     */
143    @Deprecated
144    public CmsJspNavElement(String sitePath, Map<String, String> properties) {
145
146        init(sitePath, properties, -1);
147    }
148
149    /**
150     * Create a new instance of the bean and calls the init method
151     * with the provided parameters.<p>
152     *
153     * @param sitePath will be passed to <code>init</code>
154     * @param properties will be passed to <code>init</code>
155     * @param navTreeLevel will be passed to <code>init</code>
156     *
157     * @see #init(String, Map, int)
158     *
159     * @deprecated use {@link #CmsJspNavElement(String, CmsResource, Map, int)}
160     */
161    @Deprecated
162    public CmsJspNavElement(String sitePath, Map<String, String> properties, int navTreeLevel) {
163
164        init(sitePath, properties, navTreeLevel);
165    }
166
167    /**
168     * Note: this class has a natural ordering that is inconsistent with equals.<p>
169     *
170     * @see java.lang.Comparable#compareTo(Object)
171     */
172    public int compareTo(CmsJspNavElement obj) {
173
174        if (obj == this) {
175            return 0;
176        }
177        float pos = obj.getNavPosition();
178        // please note: can't just subtract and cast to int here because of float precision loss
179        if (m_position == pos) {
180            return 0;
181        }
182        return (m_position < pos) ? -1 : 1;
183    }
184
185    /**
186     * Note: this class has a natural ordering that is inconsistent with equals.<p>
187     *
188     * @see java.lang.Object#equals(Object)
189     */
190    @Override
191    public boolean equals(Object obj) {
192
193        if (obj == this) {
194            return true;
195        }
196        if (obj instanceof CmsJspNavElement) {
197            return ((CmsJspNavElement)obj).m_sitePath.equals(m_sitePath);
198        }
199        return false;
200    }
201
202    /**
203     * Returns the value of the property PROPERTY_DESCRIPTION of this navigation element,
204     * or <code>null</code> if this property is not set.<p>
205     *
206     * @return the value of the property PROPERTY_DESCRIPTION of this navigation element
207     *          or <code>null</code> if this property is not set
208     */
209    public String getDescription() {
210
211        return m_properties.get(CmsPropertyDefinition.PROPERTY_DESCRIPTION);
212    }
213
214    /**
215     * Returns the filename of the navigation element, i.e.
216     * the name of the navigation resource without any path information.<p>
217     *
218     * @return the filename of the navigation element, i.e.
219     *          the name of the navigation resource without any path information
220     */
221    public String getFileName() {
222
223        if (m_fileName == null) {
224            // use "lazy initializing"
225            if (!m_sitePath.endsWith("/")) {
226                m_fileName = m_sitePath.substring(m_sitePath.lastIndexOf("/") + 1, m_sitePath.length());
227            } else {
228                m_fileName = m_sitePath.substring(
229                    m_sitePath.substring(0, m_sitePath.length() - 1).lastIndexOf("/") + 1,
230                    m_sitePath.length());
231            }
232        }
233        return m_fileName;
234    }
235
236    /**
237     * Returns the value of the property <code>{@link CmsPropertyDefinition#PROPERTY_NAVINFO}</code> of this
238     * navigation element, or <code>null</code> if this property is not set.<p>
239     *
240     * @return the value of the property or <code>null</code> if this property is not set
241     */
242    public String getInfo() {
243
244        return m_properties.get(CmsPropertyDefinition.PROPERTY_NAVINFO);
245    }
246
247    /**
248     * Returns the value of the property <code>{@link CmsPropertyDefinition#PROPERTY_LOCALE}</code> of this
249     * navigation element, or <code>null</code> if this property is not set.<p>
250     *
251     * @return the value of the property or <code>null</code> if this property is not set
252     */
253    public String getLocale() {
254
255        return m_properties.get(CmsPropertyDefinition.PROPERTY_LOCALE);
256    }
257
258    /**
259     * Returns the value of the property <code>{@link CmsPropertyDefinition#PROPERTY_NAVIMAGE}</code> of this
260     * navigation element, or <code>null</code> if this property is not set.<p>
261     *
262     * @return the value of the property or <code>null</code> if this property is not set
263     */
264    public String getNavImage() {
265
266        return m_properties.get(CmsPropertyDefinition.PROPERTY_NAVIMAGE);
267    }
268
269    /**
270     * Returns the value of the property C_PROPERTY_NAVPOS converted to a <code>float</code>,
271     * or a value of <code>Float.MAX_VALUE</code> if the navigation position property is not
272     * set (or not a valid number) for this resource.<p>
273     *
274     * @return float the value of the property C_PROPERTY_NAVPOS converted to a <code>float</code>,
275     *          or a value of <code>Float.MAX_VALUE</code> if the navigation position property is not
276     *          set (or not a valid number) for this resource
277     */
278    public float getNavPosition() {
279
280        return m_position;
281    }
282
283    /**
284     * Returns the value of the property PROPERTY_NAVTEXT of this navigation element,
285     * or a warning message if this property is not set
286     * (this method will never return <code>null</code>).<p>
287     *
288     * @return the value of the property PROPERTY_NAVTEXT of this navigation element,
289     *          or a warning message if this property is not set
290     *          (this method will never return <code>null</code>)
291     */
292    public String getNavText() {
293
294        if (m_text == null) {
295            // use "lazy initializing"
296            m_text = m_properties.get(CmsPropertyDefinition.PROPERTY_NAVTEXT);
297            if (m_text == null) {
298                m_text = CmsMessages.formatUnknownKey(CmsPropertyDefinition.PROPERTY_NAVTEXT);
299            }
300        }
301        return m_text;
302    }
303
304    /**
305     * Returns the navigation tree level of this resource.<p>
306     *
307     * @return the navigation tree level of this resource
308     */
309    public int getNavTreeLevel() {
310
311        if (m_navTreeLevel < 0) {
312            // use "lazy initializing"
313            m_navTreeLevel = CmsResource.getPathLevel(m_sitePath);
314        }
315        return m_navTreeLevel;
316    }
317
318    /**
319     * Returns the name of the parent folder of the resource of this navigation element.<p>
320     *
321     * @return the name of the parent folder of the resource of this navigation element
322     */
323    public String getParentFolderName() {
324
325        return CmsResource.getParentFolder(m_sitePath);
326    }
327
328    /**
329     * Returns the original map of all file properties of the resource that
330     * the navigation element belongs to.<p>
331     *
332     * Please note that the original reference is returned, so be careful when making
333     * changes to the map.<p>
334     *
335     * @return the original map of all file properties of the resource that
336     *          the navigation element belongs to
337     */
338    public Map<String, String> getProperties() {
339
340        return m_properties;
341    }
342
343    /**
344     * Returns the value of the selected property from this navigation element.<p>
345     *
346     * The navigation element contains a hash of all file properties of the resource that
347     * the navigation element belongs to.<p>
348     *
349     * @param key the property name to look up
350     *
351     * @return the value of the selected property
352     */
353    public String getProperty(String key) {
354
355        return m_properties.get(key);
356    }
357
358    /**
359     * Returns the resource.<p>
360     *
361     * @return the resource
362     */
363    public CmsResource getResource() {
364
365        return m_resource;
366    }
367
368    /**
369     * Returns the resource name this navigation element was initialized with.<p>
370     *
371     * @return the resource name this navigation element was initialized with
372     */
373    public String getResourceName() {
374
375        return m_sitePath;
376    }
377
378    /**
379     * Returns the value of the property PROPERTY_TITLE of this navigation element,
380     * or <code>null</code> if this property is not set.<p>
381     *
382     * @return the value of the property PROPERTY_TITLE of this navigation element
383     *          or <code>null</code> if this property is not set
384     */
385    public String getTitle() {
386
387        return m_properties.get(CmsPropertyDefinition.PROPERTY_TITLE);
388    }
389
390    /**
391     * Returns if the navigation position has been changed since initialization.<p>
392     *
393     * @return <code>true</code> if the navigation position has been changed since initialization
394     */
395    public boolean hasChangedNavPosition() {
396
397        return m_changedNavPos;
398    }
399
400    /**
401     * Note: this class has a natural ordering that is inconsistent with equals.<p>
402     *
403     * @see java.lang.Object#hashCode()
404     */
405    @Override
406    public int hashCode() {
407
408        return m_sitePath.hashCode();
409    }
410
411    /**
412     * Same as calling {@link #init(String, Map, int)
413     * init(String, Hashtable, -1)}.<p>
414     *
415     * @param resource the name of the resource to extract the navigation
416     *     information from
417     * @param properties the properties of the resource read from the vfs
418     */
419    public void init(String resource, Map<String, String> properties) {
420
421        init(resource, properties, -1);
422    }
423
424    /**
425     * Initialized the member variables of this bean with the values
426     * provided.<p>
427     *
428     * A resource will be in the navigation if at least one of the two properties
429     * <code>I_CmsConstants.PROPERTY_NAVTEXT</code> or
430     * <code>I_CmsConstants.PROPERTY_NAVPOS</code> is set. Otherwise
431     * it will be ignored.<p>
432     *
433     * This bean does provides static methods to create a new instance
434     * from the context of a current CmsObject. Call these static methods
435     * in order to get a properly initialized bean.<p>
436     *
437     * @param resource the name of the resource to extract the navigation
438     *     information from
439     * @param properties the properties of the resource read from the vfs
440     * @param navTreeLevel tree level of this resource, for building
441     *     navigation trees
442     *
443     * @see CmsJspNavBuilder#getNavigationForResource()
444     */
445    public void init(String resource, Map<String, String> properties, int navTreeLevel) {
446
447        m_sitePath = resource;
448        m_properties = properties;
449        m_navTreeLevel = navTreeLevel;
450        // init the position value
451        m_position = Float.MAX_VALUE;
452        try {
453            m_position = Float.parseFloat(m_properties.get(CmsPropertyDefinition.PROPERTY_NAVPOS));
454        } catch (Exception e) {
455            // m_position will have Float.MAX_VALUE, so navigation element will
456            // appear last in navigation
457        }
458    }
459
460    /**
461     * Returns <code>true</code> if this navigation element describes a folder,
462     * <code>false</code> otherwise.<p>
463     *
464     * @return <code>true</code> if this navigation element describes a folder,
465     *          <code>false</code> otherwise.<p>
466     */
467    public boolean isFolderLink() {
468
469        return m_sitePath.endsWith("/");
470    }
471
472    /**
473     * Returns <code>true</code> if this navigation element is in the navigation,
474     * <code>false</code> otherwise.<p>
475     *
476     * A resource is considered to be in the navigation, if <ol>
477     * <li>it has the property PROPERTY_NAVTEXT set
478     * <li><em>or</em> it has the property PROPERTY_NAVPOS set
479     * <li><em>and</em> it is not a temporary file as defined by {@link CmsResource#isTemporaryFileName(String)}.</ol>
480     *
481     * @return <code>true</code> if this navigation element is in the navigation, <code>false</code> otherwise
482     */
483    public boolean isInNavigation() {
484
485        if (m_hasNav == null) {
486            // use "lazy initializing"
487            Object o1 = m_properties.get(CmsPropertyDefinition.PROPERTY_NAVTEXT);
488            Object o2 = m_properties.get(CmsPropertyDefinition.PROPERTY_NAVPOS);
489            m_hasNav = Boolean.valueOf(((o1 != null) || (o2 != null)) && !CmsResource.isTemporaryFileName(m_sitePath));
490        }
491        return m_hasNav.booleanValue();
492    }
493
494    /**
495     * Returns if this is a hidden navigation entry.<p>
496     *
497     * @return <code>true</code> if this is a hidden navigation entry
498     */
499    public boolean isHiddenNavigationEntry() {
500
501        if (m_isHiddenNavigationEntry == null) {
502            // use "lazy initializing"
503            String navInfo = m_properties.get(CmsPropertyDefinition.PROPERTY_NAVINFO);
504            m_isHiddenNavigationEntry = Boolean.valueOf(CmsClientSitemapEntry.HIDDEN_NAVIGATION_ENTRY.equals(navInfo));
505        }
506        return m_isHiddenNavigationEntry.booleanValue();
507    }
508
509    /**
510     * Returns if the navigation element represents a navigation level, linking to it's first sub-element.<p>
511     *
512     * @return <code>true</code> if the navigation element represents a navigation level
513     */
514    public boolean isNavigationLevel() {
515
516        return CmsJspNavBuilder.NAVIGATION_LEVEL_FOLDER.equals(
517            m_properties.get(CmsPropertyDefinition.PROPERTY_DEFAULT_FILE));
518    }
519
520    /**
521     * Sets the value that will be returned by the {@link #getNavPosition()}
522     * method of this class.<p>
523     *
524     * @param value the value to set
525     */
526    public void setNavPosition(float value) {
527
528        m_position = value;
529        m_changedNavPos = true;
530    }
531
532    /**
533     * Returns the site path of the target resource. This may not be the same as the navigation resource.<p>
534     *
535     * @return the target resource site path
536     */
537    protected String getSitePath() {
538
539        return m_sitePath;
540    }
541
542    /**
543     * Sets the resource.<p>
544     *
545     * @param resource the resource to set
546     */
547    protected void setResource(CmsResource resource) {
548
549        m_resource = resource;
550    }
551}