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;
029
030import org.opencms.file.CmsFile;
031import org.opencms.file.CmsFolder;
032import org.opencms.file.CmsObject;
033import org.opencms.file.CmsProperty;
034import org.opencms.file.CmsPropertyDefinition;
035import org.opencms.file.CmsRequestContext;
036import org.opencms.file.CmsResource;
037import org.opencms.file.CmsResourceFilter;
038import org.opencms.file.types.CmsResourceTypeXmlContent;
039import org.opencms.file.types.CmsResourceTypeXmlPage;
040import org.opencms.i18n.CmsLocaleGroup;
041import org.opencms.jsp.util.CmsJspCategoryAccessBean;
042import org.opencms.jsp.util.CmsJspContentAccessBean;
043import org.opencms.jsp.util.CmsJspImageBean;
044import org.opencms.jsp.util.CmsJspLinkWrapper;
045import org.opencms.jsp.util.CmsJspValueTransformers.CmsLocalePropertyLoaderTransformer;
046import org.opencms.loader.CmsLoaderException;
047import org.opencms.main.CmsException;
048import org.opencms.main.CmsLog;
049import org.opencms.main.OpenCms;
050import org.opencms.relations.CmsRelation;
051import org.opencms.relations.CmsRelationFilter;
052import org.opencms.security.CmsSecurityException;
053import org.opencms.util.CmsCollectionsGenericWrapper;
054import org.opencms.util.CmsUUID;
055import org.opencms.util.CmsVfsUtil;
056
057import java.util.ArrayList;
058import java.util.Collections;
059import java.util.HashMap;
060import java.util.List;
061import java.util.Locale;
062import java.util.Map;
063import java.util.stream.Collectors;
064
065import org.apache.commons.logging.Log;
066
067import com.google.common.base.Supplier;
068import com.google.common.base.Suppliers;
069
070/**
071 * Wrapper subclass of CmsResource with some convenience methods.<p>
072 */
073public class CmsJspResourceWrapper extends CmsResource {
074
075    /** Logger instance for this class. */
076    @SuppressWarnings("unused")
077    private static final Log LOG = CmsLog.getLog(CmsJspResourceWrapper.class);
078
079    /** Serial version id. */
080    private static final long serialVersionUID = 1L;
081
082    /** Parameter value used to select outgoing relations. */
083    public static final boolean RELATIONS_OUT = true;
084
085    /** Parameter value used to select incoming relations. */
086    public static final boolean RELATIONS_IN = false;
087
088    /** All resources that are sources of incoming relations. */
089    public List<CmsJspResourceWrapper> m_incomingRelations;
090
091    /** All resources that are targets of outgoing relations. */
092    public List<CmsJspResourceWrapper> m_outgoingRelations;
093
094    /** All parent folder of this resource in the current site as a list. */
095    public List<CmsJspResourceWrapper> m_parentFolders;
096
097    /** The category access bean for this resource. */
098    private CmsJspCategoryAccessBean m_categories;
099
100    /** The CMS context. */
101    private CmsObject m_cms;
102
103    /** The resource / file content as a String. */
104    private String m_content;
105
106    /** The file object for this resource. */
107    private CmsFile m_file;
108
109    /** Image bean instance created from this resource. */
110    private CmsJspImageBean m_imageBean;
111
112    /** Stores if this resource is an XML content or not. */
113    private Boolean m_isXml;
114
115    /** The set of locale variants. */
116    private Map<String, CmsJspResourceWrapper> m_localeResources;
117
118    /** The main locale. */
119    private Locale m_mainLocale;
120
121    /** The navigation builder for this resource. */
122    private CmsJspNavBuilder m_navBuilder;
123
124    /** The navigation info element for this resource. */
125    private CmsJspNavElement m_navigation;
126
127    /** The default file of this resource, assumed that this resource is a folder. */
128    private CmsJspResourceWrapper m_navigationDefaultFile;
129
130    /** The navigation info elements in this resource, assuming that this resource is a folder. */
131    private List<CmsJspNavElement> m_navigationForFolder;
132
133    /** The parent folder of this resource in the current site. */
134    private CmsJspResourceWrapper m_parentFolder;
135
136    /** Properties of this resource. */
137    private Map<String, String> m_properties;
138
139    /** Locale properties of this resource. */
140    private Map<String, Map<String, String>> m_propertiesLocale;
141
142    /** Locale properties of this resource with search. */
143    private Map<String, Map<String, String>> m_propertiesLocaleSearch;
144
145    /** Properties of this resource with search. */
146    private Map<String, String> m_propertiesSearch;
147
148    /** The calculated site path of the resource. */
149    private String m_sitePath;
150
151    /** The type name of the resource. */
152    private String m_typeName;
153
154    /** The XML content access bean. */
155    private CmsJspContentAccessBean m_xml;
156
157    /** Creates/caches link wrapper. */
158    private Supplier<CmsJspLinkWrapper> m_linkSupplier = Suppliers.memoize(
159        () -> new CmsJspLinkWrapper(m_cms, CmsJspResourceWrapper.this));
160
161    /**
162     * Creates a new instance.<p>
163     *
164     * @param cms the current CMS context
165     * @param res the resource to wrap
166     */
167    private CmsJspResourceWrapper(CmsObject cms, CmsResource res) {
168
169        super(
170            res.getStructureId(),
171            res.getResourceId(),
172            res.getRootPath(),
173            res.getTypeId(),
174            res.isFolder(),
175            res.getFlags(),
176            res.getProjectLastModified(),
177            res.getState(),
178            res.getDateCreated(),
179            res.getUserCreated(),
180            res.getDateLastModified(),
181            res.getUserLastModified(),
182            res.getDateReleased(),
183            res.getDateExpired(),
184            res.getSiblingCount(),
185            res.getLength(),
186            res.getDateContent(),
187            res.getVersion());
188        m_cms = cms;
189        m_file = null;
190        m_content = "";
191    }
192
193    /**
194     * Factory method to create a new {@link CmsJspResourceWrapper} instance from a {@link CmsResource}.<p>
195     *
196     * In case the parameter resource already is a wrapped resource AND the OpenCms request context is
197     * the same as the provided context, the parameter object is returned.<p>
198     *
199     * @param cms the current CMS context
200     * @param res the resource to wrap
201     *
202     * @return a new instance of a {@link CmsJspResourceWrapper}
203     */
204    public static CmsJspResourceWrapper wrap(CmsObject cms, CmsResource res) {
205
206        CmsJspResourceWrapper result = null;
207        if ((cms != null) && (res != null)) {
208            if (res instanceof CmsJspResourceWrapper) {
209                CmsJspResourceWrapper wrapper = (CmsJspResourceWrapper)res;
210                if (cms.getRequestContext().getSiteRoot().equals(wrapper.getRequestContext().getSiteRoot())) {
211                    result = wrapper;
212                } else {
213                    result = new CmsJspResourceWrapper(cms, res);
214                }
215            } else {
216                result = new CmsJspResourceWrapper(cms, res);
217            }
218        }
219        return result;
220    }
221
222    /**
223     * Two resources are considered equal in case their structure id is equal.<p>
224     *
225     * @see CmsResource#equals(java.lang.Object)
226     */
227    @Override
228    public boolean equals(Object obj) {
229
230        if (obj == null) {
231            return false;
232        }
233
234        if (obj == this) {
235            return true;
236        }
237        if (obj instanceof CmsResource) {
238            return ((CmsResource)obj).getStructureId().equals(getStructureId());
239        }
240        return false;
241    }
242
243    /**
244     * Returns the categories assigned to this resource.<p>
245     *
246     * @return the categories assigned to this resource
247     */
248    public CmsJspCategoryAccessBean getCategories() {
249
250        if (m_categories == null) {
251            m_categories = new CmsJspCategoryAccessBean(m_cms, this);
252        }
253        return m_categories;
254    }
255
256    /**
257     * Returns the OpenCms user context this resource was initialized with.<p>
258     *
259     * @return the OpenCms user context this resource was initialized with
260     */
261    public CmsObject getCmsObject() {
262
263        return m_cms;
264    }
265
266    /**
267     * Returns the content of the file as a String.<p>
268     *
269     * @return the content of the file as a String
270     */
271    public String getContent() {
272
273        if ((m_content.length() == 0) && (getFile() != null)) {
274            m_content = new String(getFile().getContents());
275        }
276        return m_content;
277    }
278
279    /**
280     * Returns this resources name extension (if present).<p>
281     *
282     * The extension will always be lower case.<p>
283     *
284     * @return the extension or <code>null</code> if not available
285     *
286     * @see CmsResource#getExtension(String)
287     * @see org.opencms.jsp.util.CmsJspVfsAccessBean#getResourceExtension(Object)
288     */
289    public String getExtension() {
290
291        return getExtension(getRootPath());
292    }
293
294    /**
295     * Returns the full file object for this resource.<p>
296     *
297     * @return the full file object for this resource
298     */
299    public CmsFile getFile() {
300
301        if ((m_file == null) && !isFolder()) {
302            try {
303                m_file = m_cms.readFile(this);
304            } catch (CmsException e) {
305                // this should not happen since we are updating from a resource object
306            }
307        }
308        return m_file;
309    }
310
311    /**
312     * Returns the folder of this resource.<p>
313     *
314     * In case this resource already is a {@link CmsFolder}, it is returned without modification.
315     * In case it is a {@link CmsFile}, the parent folder of the file is returned.<p>
316     *
317     * @return the folder of this resource
318     *
319     * @see #getSitePathFolder()
320     */
321    public CmsJspResourceWrapper getFolder() {
322
323        CmsJspResourceWrapper result;
324        if (isFolder()) {
325            result = this;
326        } else {
327            result = readResource(getSitePathFolder());
328        }
329        return result;
330    }
331
332    /**
333     * Gets a list of resource wrappers for resources with relations pointing to this resource.
334     *
335     * @return the list of resource wrappers
336     */
337    public List<CmsJspResourceWrapper> getIncomingRelations() {
338
339        if (m_incomingRelations == null) {
340            m_incomingRelations = getRelatedResources(RELATIONS_IN);
341        }
342        return m_incomingRelations;
343    }
344
345    /**
346     * Gets a list of resource wrappers for resources with relations pointing to this resource, for a specific type.
347     *
348     * @param typeName name of the type to filter
349     * @return the list of resource wrappers
350     */
351    public List<CmsJspResourceWrapper> getIncomingRelations(String typeName) {
352
353        return getIncomingRelations().stream().filter(res -> res.getTypeName().equals(typeName)).collect(
354            Collectors.toList());
355    }
356
357    /**
358     * Returns <code>true</code> in case this resource is an image in the VFS.<p>
359     *
360     * @return <code>true</code> in case this resource is an image in the VFS
361     */
362    public boolean getIsImage() {
363
364        return getToImage().isImage();
365    }
366
367    /**
368     * Returns <code>true</code> in case this resource is an XML content.<p>
369     *
370     * @return <code>true</code> in case this resource is an XML content
371     */
372    public boolean getIsXml() {
373
374        if (m_isXml == null) {
375            m_isXml = Boolean.valueOf(
376                CmsResourceTypeXmlPage.isXmlPage(this) || CmsResourceTypeXmlContent.isXmlContent(this));
377        }
378        return m_isXml.booleanValue();
379    }
380
381    /**
382     * Returns a substituted link to this resource.<p>
383     *
384     * @return the link
385     */
386    public String getLink() {
387
388        return getToLink().getLink();
389    }
390
391    /**
392     * Returns a map of the locale group for the current resource, with locale strings as keys.<p>
393     *
394     * @return a map with locale strings as keys and resource wrappers for the corresponding locale variants
395     */
396    public Map<String, CmsJspResourceWrapper> getLocaleResource() {
397
398        if (m_localeResources != null) {
399            return m_localeResources;
400        }
401        try {
402            CmsLocaleGroup localeGroup = m_cms.getLocaleGroupService().readLocaleGroup(this);
403            Map<Locale, CmsResource> resourcesByLocale = localeGroup.getResourcesByLocale();
404            Map<String, CmsJspResourceWrapper> result = new HashMap<>();
405            for (Map.Entry<Locale, CmsResource> entry : resourcesByLocale.entrySet()) {
406                result.put(entry.getKey().toString(), CmsJspResourceWrapper.wrap(m_cms, entry.getValue()));
407            }
408            m_localeResources = result;
409            return result;
410        } catch (CmsException e) {
411            return new HashMap<String, CmsJspResourceWrapper>();
412        }
413    }
414
415    /**
416     * Returns the main locale for this resource.<p>
417     *
418     * @return the main locale for this resource
419     */
420    public Locale getMainLocale() {
421
422        if (m_mainLocale != null) {
423            return m_mainLocale;
424        }
425        try {
426            CmsLocaleGroup localeGroup = m_cms.getLocaleGroupService().readLocaleGroup(this);
427            m_mainLocale = localeGroup.getMainLocale();
428            return m_mainLocale;
429        } catch (CmsException e) {
430            return null;
431        }
432    }
433
434    /**
435     * Returns the mime type for this resource.<p>
436     *
437     * In case no valid mime type can be determined from the file extension, <code>text/plain</code> is returned.<p>
438     *
439     * @return the mime type for this resource
440     */
441    public String getMimeType() {
442
443        return OpenCms.getResourceManager().getMimeType(getRootPath(), null, "text/plain");
444    }
445
446    /**
447     * Returns the navigation builder for this resource.<p>
448     *
449     * This will be initialized with this resource as default URI.<p>
450     *
451     * @return the navigation builder for this resource
452     */
453    public CmsJspNavBuilder getNavBuilder() {
454
455        if (m_navBuilder == null) {
456            m_navBuilder = new CmsJspNavBuilder();
457            m_navBuilder.init(m_cms, null, getSitePath());
458        }
459        return m_navBuilder;
460    }
461
462    /**
463     * Returns the navigation info element for this resource.<p>
464     *
465     * @return the navigation info element for this resource
466     */
467    public CmsJspNavElement getNavigation() {
468
469        if (m_navigation == null) {
470            m_navigation = getNavBuilder().getNavigationForResource();
471        }
472        return m_navigation;
473    }
474
475    /**
476     * Returns the default resource for this resource.<p>
477     *
478     * If this resource is a file, then this file is returned.<p>
479     *
480     * Otherwise, in case this resource is a folder:<br>
481     * <ol>
482     *   <li>the {@link CmsPropertyDefinition#PROPERTY_DEFAULT_FILE} is checked, and
483     *   <li>if still no file could be found, the configured default files in the
484     *       <code>opencms-vfs.xml</code> configuration are iterated until a match is
485     *       found, and
486     *   <li>if still no file could be found, <code>null</code> is returned
487     * </ol>
488     *
489     * @return the default file for the given folder
490     *
491     * @see CmsObject#readDefaultFile(CmsResource, CmsResourceFilter)
492     */
493    public CmsJspResourceWrapper getNavigationDefaultFile() {
494
495        if (m_navigationDefaultFile == null) {
496            if (isFolder()) {
497                try {
498                    m_navigationDefaultFile = wrap(m_cms, m_cms.readDefaultFile(this, CmsResourceFilter.DEFAULT));
499                } catch (CmsSecurityException e) {
500                    if (LOG.isDebugEnabled()) {
501                        LOG.debug(e.getMessage(), e);
502                    }
503                }
504            }
505        } else {
506            m_navigationDefaultFile = this;
507        }
508        return m_navigationDefaultFile;
509    }
510
511    /**
512     * Returns the navigation info elements in this resource, assuming that this resource is a folder.<p>
513     *
514     * @return the navigation info elements in this resource, assuming that this resource is a folder
515     */
516    public List<CmsJspNavElement> getNavigationForFolder() {
517
518        if (m_navigationForFolder == null) {
519            m_navigationForFolder = getNavBuilder().getNavigationForFolder();
520        }
521        return m_navigationForFolder;
522    }
523
524    /**
525     * Returns the substituted online link to this resource.<p>
526     *
527     * @return the link
528     */
529    public String getOnlineLink() {
530
531        return getToLink().getOnlineLink();
532    }
533
534    /**
535     * Gets a list of resources with relations pointing to them from this resources, as resource wrappers.
536     *
537     * @return the list of resource wrappers
538     */
539    public List<CmsJspResourceWrapper> getOutgoingRelations() {
540
541        if (m_outgoingRelations == null) {
542            m_outgoingRelations = getRelatedResources(RELATIONS_OUT);
543        }
544        return m_outgoingRelations;
545    }
546
547    /**
548     * Gets a list of resources with relations pointing to them from this resources, as resource wrappers.
549     *
550     * Only gets resources with the given type.
551     *
552     * @param typeName the name of the type to filter
553     * @return the list of resource wrappers
554     */
555
556    public List<CmsJspResourceWrapper> getOutgoingRelations(String typeName) {
557
558        return getOutgoingRelations().stream().filter(res -> res.getTypeName().equals(typeName)).collect(
559            Collectors.toList());
560    }
561
562    /**
563     * Returns the parent folder of this resource in the current site.<p>
564     *
565     * The parent folder of a file is the folder of the file.
566     * The parent folder of a folder is the parent folder of the folder.
567     * The parent folder of the root folder is <code>null</code>.<p>
568     *
569     * @return the parent folder of this resource in the current site
570     *
571     * @see #getSitePathParentFolder()
572     * @see CmsResource#getParentFolder(String)
573     * @see org.opencms.jsp.util.CmsJspVfsAccessBean#getParentFolder(Object)
574     */
575    public CmsJspResourceWrapper getParentFolder() {
576
577        if (m_parentFolder == null) {
578            String parentFolder = getSitePathParentFolder();
579            if (parentFolder != null) {
580                m_parentFolder = readResource(getSitePathParentFolder());
581            }
582        }
583        return m_parentFolder;
584    }
585
586    /**
587     * Returns all parent folder of this resource in the current site as a list.<p>
588     *
589     * First resource in the list will be the direct parent folder of this resource,
590     * the last element will be the site root folder.<p>
591     *
592     * @return all parent folder of this resource in the current site as a list
593     */
594    public List<CmsJspResourceWrapper> getParentFolders() {
595
596        if (m_parentFolders == null) {
597            m_parentFolders = new ArrayList<CmsJspResourceWrapper>();
598            CmsJspResourceWrapper parentFolder = getParentFolder();
599            while (parentFolder != null) {
600                m_parentFolders.add(parentFolder);
601                parentFolder = parentFolder.getParentFolder();
602            }
603        }
604        return m_parentFolders;
605    }
606
607    /**
608     * Returns the direct properties of this resource in a map.<p>
609     *
610     * This is without "search", so it will not include inherited properties from the parent folders.<p>
611     *
612     * @return the direct properties of this resource in a map
613     */
614    public Map<String, String> getProperty() {
615
616        if (m_properties == null) {
617            try {
618                List<CmsProperty> properties = m_cms.readPropertyObjects(this, false);
619                m_properties = CmsProperty.toMap(properties);
620            } catch (CmsException e) {
621                if (LOG.isDebugEnabled()) {
622                    LOG.debug(e.getMessage(), e);
623                }
624            }
625        }
626        return m_properties;
627    }
628
629    /**
630     * Returns the locale specific evaluated "Description" property for the resource.
631     * In case the resource is a default file, also the parent folders "Description" property is read as fallback.
632     *
633     * @return the locale specific description property for the resource.
634     */
635    public String getPropertyDescription() {
636
637        return CmsVfsUtil.readPropertyValueWithFolderFallbackForDefaultFiles(
638            m_cms,
639            this,
640            CmsPropertyDefinition.PROPERTY_DESCRIPTION,
641            m_cms.getRequestContext().getLocale());
642    }
643
644    /**
645     * Returns the locale specific evaluated "Keywords" property for the resource.
646     * In case the resource is a default file, also the parent folders "Keywords" property is read as fallback.
647     *
648     * @return the locale specific description property for the resource.
649     */
650    public String getPropertyKeywords() {
651
652        return CmsVfsUtil.readPropertyValueWithFolderFallbackForDefaultFiles(
653            m_cms,
654            this,
655            CmsPropertyDefinition.PROPERTY_KEYWORDS,
656            m_cms.getRequestContext().getLocale());
657    }
658
659    /**
660     * Returns the direct properties of this resource in a map for a given locale.<p>
661     *
662     * This is without "search", so it will not include inherited properties from the parent folders.<p>
663     *
664     * @return the direct properties of this resource in a map for  a given locale
665     */
666    public Map<String, Map<String, String>> getPropertyLocale() {
667
668        if (m_propertiesLocale == null) {
669            m_propertiesLocale = CmsCollectionsGenericWrapper.createLazyMap(
670                new CmsLocalePropertyLoaderTransformer(getCmsObject(), this, false));
671            // result may still be null
672            return (m_propertiesLocale == null) ? Collections.EMPTY_MAP : m_propertiesLocale;
673        }
674        return m_propertiesLocale;
675    }
676
677    /**
678     * Returns the searched properties of this resource in a map for a given locale.<p>
679     *
680     * This is with "search", so it will include inherited properties from the parent folders.<p>
681     *
682     * @return the direct properties of this resource in a map for a given locale
683     */
684    public Map<String, Map<String, String>> getPropertyLocaleSearch() {
685
686        if (m_propertiesLocaleSearch == null) {
687            m_propertiesLocaleSearch = CmsCollectionsGenericWrapper.createLazyMap(
688                new CmsLocalePropertyLoaderTransformer(getCmsObject(), this, true));
689            // result may still be null
690            return (m_propertiesLocaleSearch == null) ? Collections.EMPTY_MAP : m_propertiesLocaleSearch;
691        }
692        return m_propertiesLocaleSearch;
693    }
694
695    /**
696     * Returns the searched properties of this resource in a map.<p>
697     *
698     * This is with "search", so it will include inherited properties from the parent folders.<p>
699     *
700     * @return the direct properties of this resource in a map
701     */
702    public Map<String, String> getPropertySearch() {
703
704        if (m_propertiesSearch == null) {
705            try {
706                List<CmsProperty> properties = m_cms.readPropertyObjects(this, true);
707                m_propertiesSearch = CmsProperty.toMap(properties);
708            } catch (CmsException e) {
709                if (LOG.isDebugEnabled()) {
710                    LOG.debug(e.getMessage(), e);
711                }
712            }
713        }
714        return m_propertiesSearch;
715    }
716
717    /**
718     * Returns the OpenCms user request context this resource was initialized with.<p>
719     *
720     * @return the OpenCms user request context this resource was initialized with
721     */
722    public CmsRequestContext getRequestContext() {
723
724        return m_cms.getRequestContext();
725    }
726
727    /**
728     * Returns this resources name extension (if present).<p>
729     *
730     * The extension will always be lower case.<p>
731     *
732     * @return the extension or <code>null</code> if not available
733     *
734     * @see CmsResource#getExtension(String)
735     * @see org.opencms.jsp.util.CmsJspVfsAccessBean#getResourceExtension(Object)
736     */
737    public String getResourceExtension() {
738
739        return getExtension();
740    }
741
742    /**
743     * Returns the name of this resource without the path information.<p>
744     *
745     * The resource name of a file is the name of the file.
746     * The resource name of a folder is the folder name with trailing "/".
747     * The resource name of the root folder is <code>/</code>.<p>
748     *
749     * @return the name of this resource without the path information
750     *
751     * @see CmsResource#getName()
752     * @see org.opencms.jsp.util.CmsJspVfsAccessBean#getResourceName(Object)
753     */
754    public String getResourceName() {
755
756        return getName();
757    }
758
759    /**
760     * Returns the folder name of this resource from the root site.<p>
761     *
762     * In case this resource already is a {@link CmsFolder}, the folder path is returned without modification.
763     * In case it is a {@link CmsFile}, the parent folder name of the file is returned.<p>
764     *
765     * @return  the folder name of this resource from the root site
766     */
767    public String getRootPathFolder() {
768
769        String result;
770        if (isFile()) {
771            result = getRootPathParentFolder();
772        } else {
773            result = getRootPath();
774        }
775        return result;
776    }
777
778    /**
779     * Returns the directory level of a resource from the root site.<p>
780     *
781     * The root folder "/" has level 0,
782     * a folder "/foo/" would have level 1,
783     * a folder "/foo/bar/" level 2 etc.<p>
784     *
785     * @return the directory level of a resource from the root site
786     *
787     * @see CmsResource#getPathLevel(String)
788     */
789    public int getRootPathLevel() {
790
791        return getPathLevel(getRootPath());
792    }
793
794    /**
795     * Returns the parent folder of this resource from the root site.<p>
796     *
797     * @return the parent folder of this resource from the root site
798     *
799     * @see CmsResource#getParentFolder(String)
800     */
801    public String getRootPathParentFolder() {
802
803        return getParentFolder(getRootPath());
804    }
805
806    /**
807     * Returns the current site path to this resource.<p>
808     *
809     * @return the current site path to this resource
810     *
811     * @see org.opencms.file.CmsRequestContext#getSitePath(CmsResource)
812     */
813    public String getSitePath() {
814
815        if (m_sitePath == null) {
816            m_sitePath = m_cms.getRequestContext().getSitePath(this);
817        }
818
819        return m_sitePath;
820    }
821
822    /**
823     * Returns the folder name of this resource in the current site.<p>
824     *
825     * In case this resource already is a {@link CmsFolder}, the folder path is returned without modification.
826     * In case it is a {@link CmsFile}, the parent folder name of the file is returned.<p>
827     *
828     * @return  the folder name of this resource in the current site
829     */
830    public String getSitePathFolder() {
831
832        String result;
833        if (isFile()) {
834            result = getSitePathParentFolder();
835        } else {
836            result = getSitePath();
837        }
838        return result;
839    }
840
841    /**
842     * Returns the directory level of a resource in the current site.<p>
843     *
844     * The root folder "/" has level 0,
845     * a folder "/foo/" would have level 1,
846     * a folder "/foo/bar/" level 2 etc.<p>
847     *
848     * @return the directory level of a resource in the current site
849     *
850     * @see CmsResource#getPathLevel(String)
851     * @see org.opencms.jsp.util.CmsJspVfsAccessBean#getPathLevel(Object)
852     */
853    public int getSitePathLevel() {
854
855        return getPathLevel(getSitePath());
856    }
857
858    /**
859     * Returns the parent folder of this resource in the current site.<p>
860     *
861     * The parent folder of a file is the folder of the file.
862     * The parent folder of a folder is the parent folder of the folder.
863     * The parent folder of the root folder is <code>null</code>.<p>
864     *
865     * @return the parent folder of this resource in the current site
866     *
867     * @see CmsResource#getParentFolder(String)
868     * @see org.opencms.jsp.util.CmsJspVfsAccessBean#getParentFolder(Object)
869     */
870    public String getSitePathParentFolder() {
871
872        return getParentFolder(getSitePath());
873    }
874
875    /**
876     * Returns a scaled image bean from the wrapped value.<p>
877     *
878     * In case the value does not point to an image resource, <code>null</code> is returned.
879     *
880     * @return the scaled image bean
881     */
882    public CmsJspImageBean getToImage() {
883
884        if (m_imageBean == null) {
885            m_imageBean = new CmsJspImageBean(getCmsObject(), this, null);
886        }
887        return m_imageBean;
888    }
889
890    /**
891     * Gets a link wrapper corresponding to the the resource's path.
892     *
893     * @return the new link wrapper
894     */
895    public CmsJspLinkWrapper getToLink() {
896
897        return m_linkSupplier.get();
898    }
899
900    /**
901     * Returns this resource wrapper.<p>
902     *
903     * This is included because in case {@link org.opencms.jsp.util.CmsJspStandardContextBean#getWrap()} is used, the result may be
904     * either a {@link org.opencms.jsp.util.CmsJspObjectValueWrapper} or a {@link CmsJspResourceWrapper}.
905     * Using {@link #getToResource()} on the result will always return a resource wrapper this way.<p>
906     *
907     * @return this resource wrapper
908     *
909     * @see org.opencms.jsp.util.CmsJspStandardContextBean#getWrap()
910     * @see org.opencms.jsp.util.CmsJspObjectValueWrapper#getToResource()
911     */
912    public CmsJspResourceWrapper getToResource() {
913
914        return this;
915    }
916
917    /**
918     * Returns an XML content access bean created for this resource.<p>
919     *
920     * In case this resource is not an XML content, <code>null</code> is returned.<p>
921     *
922     * @return an XML content access bean created for this resource
923     *
924     * @see #getIsXml()
925     */
926    public CmsJspContentAccessBean getToXml() {
927
928        if ((m_xml == null) && getIsXml()) {
929            m_xml = new CmsJspContentAccessBean(m_cms, this);
930        }
931        return m_xml;
932    }
933
934    /**
935     * Returns the resource type name.<p>
936     *
937     * @return the resource type name
938     */
939    public String getTypeName() {
940
941        if (m_typeName == null) {
942            try {
943                m_typeName = OpenCms.getResourceManager().getResourceType(getTypeId()).getTypeName();
944            } catch (CmsLoaderException e) {
945                // this should never happen, and anyway it is logged in the resource manage already
946            }
947        }
948        return m_typeName;
949    }
950
951    /**
952     * Returns an XML content access bean created for this resource.<p>
953     *
954     * In case this resource is not an XML content, <code>null</code> is returned.<p>
955     *
956     * @return an XML content access bean created for this resource
957     *
958     * @see #getToXml()
959     * @see #getIsXml()
960     */
961    public CmsJspContentAccessBean getXml() {
962
963        return getToXml();
964    }
965
966    /**
967     * @see CmsResource#hashCode()
968     * @see java.lang.Object#hashCode()
969     */
970    @Override
971    public int hashCode() {
972
973        if (getStructureId() != null) {
974            return getStructureId().hashCode();
975        }
976
977        return CmsUUID.getNullUUID().hashCode();
978    }
979
980    /**
981     * Returns <code>true</code> in case this resource is child resource of the provided resource which is assumed to be a folder.<p>
982     *
983     * @param resource the resource to check
984     *
985     * @return <code>true</code> in case this resource is child resource of the provided resource which is assumed to be a folder
986     */
987    public boolean isChildResourceOf(CmsResource resource) {
988
989        return (resource != null)
990            && resource.isFolder()
991            && !(getStructureId().equals(resource.getStructureId()))
992            && ((getRootPath().indexOf(resource.getRootPath()) == 0));
993    }
994
995    /**
996     * Returns <code>true</code> in case this resource is child resource of the provided resource path which is assumed to be a folder in the current site.<p>
997     *
998     * No check is performed to see if the provided site path resource actually exists.<p>
999     *
1000     * @param sitePath the resource to check
1001     *
1002     * @return <code>true</code> in case this resource is child resource of the provided resource path which is assumed to be a folder in the current site
1003     */
1004    public boolean isChildResourceOf(String sitePath) {
1005
1006        return (sitePath != null)
1007            && ((getSitePath().indexOf(sitePath) == 0))
1008            && (sitePath.length() < getSitePath().length());
1009    }
1010
1011    public boolean isNavigationDefaultFile() {
1012
1013        return CmsVfsUtil.isDefaultFile(m_cms, this);
1014    }
1015
1016    /**
1017     * Returns <code>true</code> in case this resource is a parent folder of the provided resource.<p>
1018     *
1019     * @param resource the resource to check
1020     *
1021     * @return <code>true</code> in case this resource is a parent folder of the provided resource
1022     */
1023    public boolean isParentFolderOf(CmsResource resource) {
1024
1025        return (resource != null)
1026            && isFolder()
1027            && !(getStructureId().equals(resource.getStructureId()))
1028            && ((resource.getRootPath().indexOf(getRootPath()) == 0));
1029    }
1030
1031    /**
1032     * Returns <code>true</code> in case this resource is a parent folder of the provided resource path in the current site.<p>
1033     *
1034     * No check is performed to see if the provided site path resource actually exists.<p>
1035     *
1036     * @param sitePath the path to check
1037     *
1038     * @return <code>true</code> in case this resource is a parent folder of the provided resource path in the current site
1039     */
1040    public boolean isParentFolderOf(String sitePath) {
1041
1042        return (sitePath != null)
1043            && isFolder()
1044            && ((sitePath.indexOf(getSitePath()) == 0))
1045            && (sitePath.length() > getSitePath().length());
1046    }
1047
1048    /**
1049     * Helper method for getting the related resources for this resource, with a given resource filter.
1050     *
1051     * @param out - true for outgoing relations, false for incoming relations
1052     * @return the list of related resources
1053     */
1054    private List<CmsJspResourceWrapper> getRelatedResources(boolean out) {
1055
1056        CmsObject cms = getCmsObject();
1057        List<CmsJspResourceWrapper> result = new ArrayList<>();
1058        try {
1059            CmsRelationFilter filter = out
1060            ? CmsRelationFilter.relationsFromStructureId(getStructureId())
1061            : CmsRelationFilter.relationsToStructureId(getStructureId());
1062            List<CmsRelation> relations = cms.readRelations(filter);
1063            for (CmsRelation rel : relations) {
1064                try {
1065                    CmsResource other = out
1066                    ? rel.getTarget(cms, CmsResourceFilter.DEFAULT)
1067                    : rel.getSource(cms, CmsResourceFilter.DEFAULT);
1068                    result.add(wrap(cms, other));
1069                } catch (CmsException e) {
1070                    LOG.warn(e.getLocalizedMessage(), e);
1071                }
1072            }
1073        } catch (Exception e) {
1074            LOG.error(e.getLocalizedMessage(), e);
1075        }
1076        return result;
1077    }
1078
1079    /**
1080     * Reads a resource, suppressing possible exceptions.<p>
1081     *
1082     * @param sitePath the site path of the resource to read.
1083     *
1084     * @return the resource of <code>null</code> on case an exception occurred while reading
1085     */
1086    private CmsJspResourceWrapper readResource(String sitePath) {
1087
1088        CmsJspResourceWrapper result = null;
1089        try {
1090            result = new CmsJspResourceWrapper(m_cms, m_cms.readResource(sitePath));
1091        } catch (CmsException e) {
1092            if (LOG.isDebugEnabled()) {
1093                LOG.debug(e.getMessage(), e);
1094            }
1095        }
1096        return result;
1097    }
1098}