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