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