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     * Gets a list of resources with relations pointing to them from this resources, as resource wrappers.
519     *
520     * @return the list of resource wrappers
521     */
522    public List<CmsJspResourceWrapper> getOutgoingRelations() {
523
524        if (m_outgoingRelations == null) {
525            m_outgoingRelations = getRelatedResources(RELATIONS_OUT);
526        }
527        return m_outgoingRelations;
528    }
529
530    /**
531     * Gets a list of resources with relations pointing to them from this resources, as resource wrappers.
532     *
533     * Only gets resources with the given type.
534     *
535     * @param typeName the name of the type to filter
536     * @return the list of resource wrappers
537     */
538
539    public List<CmsJspResourceWrapper> getOutgoingRelations(String typeName) {
540
541        return getOutgoingRelations().stream().filter(res -> res.getTypeName().equals(typeName)).collect(
542            Collectors.toList());
543    }
544
545    /**
546     * Returns the parent folder of this resource in the current site.<p>
547     *
548     * The parent folder of a file is the folder of the file.
549     * The parent folder of a folder is the parent folder of the folder.
550     * The parent folder of the root folder is <code>null</code>.<p>
551     *
552     * @return the parent folder of this resource in the current site
553     *
554     * @see #getSitePathParentFolder()
555     * @see CmsResource#getParentFolder(String)
556     * @see org.opencms.jsp.util.CmsJspVfsAccessBean#getParentFolder(Object)
557     */
558    public CmsJspResourceWrapper getParentFolder() {
559
560        if (m_parentFolder == null) {
561            String parentFolder = getSitePathParentFolder();
562            if (parentFolder != null) {
563                m_parentFolder = readResource(getSitePathParentFolder());
564            }
565        }
566        return m_parentFolder;
567    }
568
569    /**
570     * Returns all parent folder of this resource in the current site as a list.<p>
571     *
572     * First resource in the list will be the direct parent folder of this resource,
573     * the last element will be the site root folder.<p>
574     *
575     * @return all parent folder of this resource in the current site as a list
576     */
577    public List<CmsJspResourceWrapper> getParentFolders() {
578
579        if (m_parentFolders == null) {
580            m_parentFolders = new ArrayList<CmsJspResourceWrapper>();
581            CmsJspResourceWrapper parentFolder = getParentFolder();
582            while (parentFolder != null) {
583                m_parentFolders.add(parentFolder);
584                parentFolder = parentFolder.getParentFolder();
585            }
586        }
587        return m_parentFolders;
588    }
589
590    /**
591     * Returns the direct properties of this resource in a map.<p>
592     *
593     * This is without "search", so it will not include inherited properties from the parent folders.<p>
594     *
595     * @return the direct properties of this resource in a map
596     */
597    public Map<String, String> getProperty() {
598
599        if (m_properties == null) {
600            try {
601                List<CmsProperty> properties = m_cms.readPropertyObjects(this, false);
602                m_properties = CmsProperty.toMap(properties);
603            } catch (CmsException e) {
604                if (LOG.isDebugEnabled()) {
605                    LOG.debug(e.getMessage(), e);
606                }
607            }
608        }
609        return m_properties;
610    }
611
612    /**
613     * Returns the direct properties of this resource in a map for a given locale.<p>
614     *
615     * This is without "search", so it will not include inherited properties from the parent folders.<p>
616     *
617     * @return the direct properties of this resource in a map for  a given locale
618     */
619    public Map<String, Map<String, String>> getPropertyLocale() {
620
621        if (m_propertiesLocale == null) {
622            m_propertiesLocale = CmsCollectionsGenericWrapper.createLazyMap(
623                new CmsLocalePropertyLoaderTransformer(getCmsObject(), this, false));
624            // result may still be null
625            return (m_propertiesLocale == null) ? Collections.EMPTY_MAP : m_propertiesLocale;
626        }
627        return m_propertiesLocale;
628    }
629
630    /**
631     * Returns the searched properties of this resource in a map for a given locale.<p>
632     *
633     * This is with "search", so it will include inherited properties from the parent folders.<p>
634     *
635     * @return the direct properties of this resource in a map for a given locale
636     */
637    public Map<String, Map<String, String>> getPropertyLocaleSearch() {
638
639        if (m_propertiesLocaleSearch == null) {
640            m_propertiesLocaleSearch = CmsCollectionsGenericWrapper.createLazyMap(
641                new CmsLocalePropertyLoaderTransformer(getCmsObject(), this, true));
642            // result may still be null
643            return (m_propertiesLocaleSearch == null) ? Collections.EMPTY_MAP : m_propertiesLocaleSearch;
644        }
645        return m_propertiesLocaleSearch;
646    }
647
648    /**
649     * Returns the searched properties of this resource in a map.<p>
650     *
651     * This is with "search", so it will include inherited properties from the parent folders.<p>
652     *
653     * @return the direct properties of this resource in a map
654     */
655    public Map<String, String> getPropertySearch() {
656
657        if (m_propertiesSearch == null) {
658            try {
659                List<CmsProperty> properties = m_cms.readPropertyObjects(this, true);
660                m_propertiesSearch = CmsProperty.toMap(properties);
661            } catch (CmsException e) {
662                if (LOG.isDebugEnabled()) {
663                    LOG.debug(e.getMessage(), e);
664                }
665            }
666        }
667        return m_propertiesSearch;
668    }
669
670    /**
671     * Returns the OpenCms user request context this resource was initialized with.<p>
672     *
673     * @return the OpenCms user request context this resource was initialized with
674     */
675    public CmsRequestContext getRequestContext() {
676
677        return m_cms.getRequestContext();
678    }
679
680    /**
681     * Returns this resources name extension (if present).<p>
682     *
683     * The extension will always be lower case.<p>
684     *
685     * @return the extension or <code>null</code> if not available
686     *
687     * @see CmsResource#getExtension(String)
688     * @see org.opencms.jsp.util.CmsJspVfsAccessBean#getResourceExtension(Object)
689     */
690    public String getResourceExtension() {
691
692        return getExtension();
693    }
694
695    /**
696     * Returns the name of this resource without the path information.<p>
697     *
698     * The resource name of a file is the name of the file.
699     * The resource name of a folder is the folder name with trailing "/".
700     * The resource name of the root folder is <code>/</code>.<p>
701     *
702     * @return the name of this resource without the path information
703     *
704     * @see CmsResource#getName()
705     * @see org.opencms.jsp.util.CmsJspVfsAccessBean#getResourceName(Object)
706     */
707    public String getResourceName() {
708
709        return getName();
710    }
711
712    /**
713     * Returns the folder name of this resource from the root site.<p>
714     *
715     * In case this resource already is a {@link CmsFolder}, the folder path is returned without modification.
716     * In case it is a {@link CmsFile}, the parent folder name of the file is returned.<p>
717     *
718     * @return  the folder name of this resource from the root site
719     */
720    public String getRootPathFolder() {
721
722        String result;
723        if (isFile()) {
724            result = getRootPathParentFolder();
725        } else {
726            result = getRootPath();
727        }
728        return result;
729    }
730
731    /**
732     * Returns the directory level of a resource from the root site.<p>
733     *
734     * The root folder "/" has level 0,
735     * a folder "/foo/" would have level 1,
736     * a folder "/foo/bar/" level 2 etc.<p>
737     *
738     * @return the directory level of a resource from the root site
739     *
740     * @see CmsResource#getPathLevel(String)
741     */
742    public int getRootPathLevel() {
743
744        return getPathLevel(getRootPath());
745    }
746
747    /**
748     * Returns the parent folder of this resource from the root site.<p>
749     *
750     * @return the parent folder of this resource from the root site
751     *
752     * @see CmsResource#getParentFolder(String)
753     */
754    public String getRootPathParentFolder() {
755
756        return getParentFolder(getRootPath());
757    }
758
759    /**
760     * Returns the current site path to this resource.<p>
761     *
762     * @return the current site path to this resource
763     *
764     * @see org.opencms.file.CmsRequestContext#getSitePath(CmsResource)
765     */
766    public String getSitePath() {
767
768        if (m_sitePath == null) {
769            m_sitePath = m_cms.getRequestContext().getSitePath(this);
770        }
771
772        return m_sitePath;
773    }
774
775    /**
776     * Returns the folder name of this resource in the current site.<p>
777     *
778     * In case this resource already is a {@link CmsFolder}, the folder path is returned without modification.
779     * In case it is a {@link CmsFile}, the parent folder name of the file is returned.<p>
780     *
781     * @return  the folder name of this resource in the current site
782     */
783    public String getSitePathFolder() {
784
785        String result;
786        if (isFile()) {
787            result = getSitePathParentFolder();
788        } else {
789            result = getSitePath();
790        }
791        return result;
792    }
793
794    /**
795     * Returns the directory level of a resource in the current site.<p>
796     *
797     * The root folder "/" has level 0,
798     * a folder "/foo/" would have level 1,
799     * a folder "/foo/bar/" level 2 etc.<p>
800     *
801     * @return the directory level of a resource in the current site
802     *
803     * @see CmsResource#getPathLevel(String)
804     * @see org.opencms.jsp.util.CmsJspVfsAccessBean#getPathLevel(Object)
805     */
806    public int getSitePathLevel() {
807
808        return getPathLevel(getSitePath());
809    }
810
811    /**
812     * Returns the parent folder of this resource in the current site.<p>
813     *
814     * The parent folder of a file is the folder of the file.
815     * The parent folder of a folder is the parent folder of the folder.
816     * The parent folder of the root folder is <code>null</code>.<p>
817     *
818     * @return the parent folder of this resource in the current site
819     *
820     * @see CmsResource#getParentFolder(String)
821     * @see org.opencms.jsp.util.CmsJspVfsAccessBean#getParentFolder(Object)
822     */
823    public String getSitePathParentFolder() {
824
825        return getParentFolder(getSitePath());
826    }
827
828    /**
829     * Returns a scaled image bean from the wrapped value.<p>
830     *
831     * In case the value does not point to an image resource, <code>null</code> is returned.
832     *
833     * @return the scaled image bean
834     */
835    public CmsJspImageBean getToImage() {
836
837        if (m_imageBean == null) {
838            m_imageBean = new CmsJspImageBean(getCmsObject(), this, null);
839        }
840        return m_imageBean;
841    }
842
843    /**
844     * Returns this resource wrapper.<p>
845     *
846     * This is included because in case {@link org.opencms.jsp.util.CmsJspStandardContextBean#getWrap()} is used, the result may be
847     * either a {@link org.opencms.jsp.util.CmsJspObjectValueWrapper} or a {@link CmsJspResourceWrapper}.
848     * Using {@link #getToResource()} on the result will always return a resource wrapper this way.<p>
849     *
850     * @return this resource wrapper
851     *
852     * @see org.opencms.jsp.util.CmsJspStandardContextBean#getWrap()
853     * @see org.opencms.jsp.util.CmsJspObjectValueWrapper#getToResource()
854     */
855    public CmsJspResourceWrapper getToResource() {
856
857        return this;
858    }
859
860    /**
861     * Returns an XML content access bean created for this resource.<p>
862     *
863     * In case this resource is not an XML content, <code>null</code> is returned.<p>
864     *
865     * @return an XML content access bean created for this resource
866     *
867     * @see #getIsXml()
868     */
869    public CmsJspContentAccessBean getToXml() {
870
871        if ((m_xml == null) && getIsXml()) {
872            m_xml = new CmsJspContentAccessBean(m_cms, this);
873        }
874        return m_xml;
875    }
876
877    /**
878     * Returns the resource type name.<p>
879     *
880     * @return the resource type name
881     */
882    public String getTypeName() {
883
884        if (m_typeName == null) {
885            try {
886                m_typeName = OpenCms.getResourceManager().getResourceType(getTypeId()).getTypeName();
887            } catch (CmsLoaderException e) {
888                // this should never happen, and anyway it is logged in the resource manage already
889            }
890        }
891        return m_typeName;
892    }
893
894    /**
895     * Returns an XML content access bean created for this resource.<p>
896     *
897     * In case this resource is not an XML content, <code>null</code> is returned.<p>
898     *
899     * @return an XML content access bean created for this resource
900     *
901     * @see #getToXml()
902     * @see #getIsXml()
903     */
904    public CmsJspContentAccessBean getXml() {
905
906        return getToXml();
907    }
908
909    /**
910     * @see CmsResource#hashCode()
911     * @see java.lang.Object#hashCode()
912     */
913    @Override
914    public int hashCode() {
915
916        if (getStructureId() != null) {
917            return getStructureId().hashCode();
918        }
919
920        return CmsUUID.getNullUUID().hashCode();
921    }
922
923    /**
924     * Returns <code>true</code> in case this resource is child resource of the provided resource which is assumed to be a folder.<p>
925     *
926     * @param resource the resource to check
927     *
928     * @return <code>true</code> in case this resource is child resource of the provided resource which is assumed to be a folder
929     */
930    public boolean isChildResourceOf(CmsResource resource) {
931
932        return (resource != null)
933            && resource.isFolder()
934            && !(getStructureId().equals(resource.getStructureId()))
935            && ((getRootPath().indexOf(resource.getRootPath()) == 0));
936    }
937
938    /**
939     * 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>
940     *
941     * No check is performed to see if the provided site path resource actually exists.<p>
942     *
943     * @param sitePath the resource to check
944     *
945     * @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
946     */
947    public boolean isChildResourceOf(String sitePath) {
948
949        return (sitePath != null)
950            && ((getSitePath().indexOf(sitePath) == 0))
951            && (sitePath.length() < getSitePath().length());
952    }
953
954    /**
955     * Returns <code>true</code> in case this resource is a parent folder of the provided resource.<p>
956     *
957     * @param resource the resource to check
958     *
959     * @return <code>true</code> in case this resource is a parent folder of the provided resource
960     */
961    public boolean isParentFolderOf(CmsResource resource) {
962
963        return (resource != null)
964            && isFolder()
965            && !(getStructureId().equals(resource.getStructureId()))
966            && ((resource.getRootPath().indexOf(getRootPath()) == 0));
967    }
968
969    /**
970     * Returns <code>true</code> in case this resource is a parent folder of the provided resource path in the current site.<p>
971     *
972     * No check is performed to see if the provided site path resource actually exists.<p>
973     *
974     * @param sitePath the path to check
975     *
976     * @return <code>true</code> in case this resource is a parent folder of the provided resource path in the current site
977     */
978    public boolean isParentFolderOf(String sitePath) {
979
980        return (sitePath != null)
981            && isFolder()
982            && ((sitePath.indexOf(getSitePath()) == 0))
983            && (sitePath.length() > getSitePath().length());
984    }
985
986    /**
987     * Helper method for getting the related resources for this resource, with a given resource filter.
988     *
989     * @param out - true for outgoing relations, false for incoming relations
990     * @return the list of related resources
991     */
992    private List<CmsJspResourceWrapper> getRelatedResources(boolean out) {
993
994        CmsObject cms = getCmsObject();
995        List<CmsJspResourceWrapper> result = new ArrayList<>();
996        try {
997            CmsRelationFilter filter = out
998            ? CmsRelationFilter.relationsFromStructureId(getStructureId())
999            : CmsRelationFilter.relationsToStructureId(getStructureId());
1000            List<CmsRelation> relations = cms.readRelations(filter);
1001            for (CmsRelation rel : relations) {
1002                try {
1003                    CmsResource other = out
1004                    ? rel.getTarget(cms, CmsResourceFilter.DEFAULT)
1005                    : rel.getSource(cms, CmsResourceFilter.DEFAULT);
1006                    result.add(wrap(cms, other));
1007                } catch (CmsException e) {
1008                    LOG.warn(e.getLocalizedMessage(), e);
1009                }
1010            }
1011        } catch (Exception e) {
1012            LOG.error(e.getLocalizedMessage(), e);
1013        }
1014        return result;
1015    }
1016
1017    /**
1018     * Reads a resource, suppressing possible exceptions.<p>
1019     *
1020     * @param sitePath the site path of the resource to read.
1021     *
1022     * @return the resource of <code>null</code> on case an exception occurred while reading
1023     */
1024    private CmsJspResourceWrapper readResource(String sitePath) {
1025
1026        CmsJspResourceWrapper result = null;
1027        try {
1028            result = new CmsJspResourceWrapper(m_cms, m_cms.readResource(sitePath));
1029        } catch (CmsException e) {
1030            if (LOG.isDebugEnabled()) {
1031                LOG.debug(e.getMessage(), e);
1032            }
1033        }
1034        return result;
1035    }
1036}