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 GmbH & Co. KG, 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.file;
029
030import org.opencms.db.CmsDriverManager;
031import org.opencms.db.CmsResourceState;
032import org.opencms.file.types.I_CmsResourceType;
033import org.opencms.main.CmsIllegalArgumentException;
034import org.opencms.ui.CmsUserIconHelper;
035import org.opencms.util.A_CmsModeIntEnumeration;
036import org.opencms.util.CmsStringUtil;
037import org.opencms.util.CmsUUID;
038
039import java.io.Serializable;
040import java.util.Arrays;
041import java.util.List;
042
043/**
044 * Base class for all OpenCms VFS resources like <code>{@link CmsFile}</code> or <code>{@link CmsFolder}</code>.<p>
045 *
046 * The OpenCms VFS resource is an important object for using the OpenCms API.
047 * Basically, all entries in the OpenCms VFS are considered to be "resources".
048 * Currently, only two types of resources exists:<ul>
049 * <li>Files, which are represented by the subclass {@link CmsFile}.
050 * <li>Folders (also called Directories), which are represented by the subclass {@link CmsFolder}.
051 * </ul>
052 *
053 * If you have a resource, you can use {@link #isFile()} or {@link #isFolder()} to learn what kind of
054 * subclass you have. Please note that this is usually not required, as the only real difference between a
055 * {@link CmsFile} and a {@link CmsResource} is that the {@link CmsFile} also has the contents of the file,
056 * which you can obtain using {@link CmsFile#getContents()}. As long as you don't need the content, you can
057 * use the {@link CmsResource} for everything else. This is even more true for a {@link CmsFolder}, here you
058 * will need the subclass only in special cases, since the signature is identical to {@link CmsResource}.<p>
059 *
060 * A OpenCms VFS resource can have any number of properties attached, which are represented by a {@link CmsProperty}.
061 * To read the properties for a resource, use {@link CmsObject#readPropertyObject(CmsResource, String, boolean)}
062 * or use {@link CmsObject#readPropertyObjects(CmsResource, boolean)} to read all properties of the resource.<p>
063 *
064 * @since 6.0.0
065 */
066public class CmsResource implements I_CmsResource, Cloneable, Serializable, Comparable<I_CmsResource> {
067
068    /**
069     *  Enumeration class for resource copy modes.<p>
070     */
071    public static final class CmsResourceCopyMode extends A_CmsModeIntEnumeration {
072
073        /** Copy mode for copy resources as new resource. */
074        protected static final CmsResourceCopyMode MODE_COPY_AS_NEW = new CmsResourceCopyMode(1);
075
076        /** Copy mode for copy resources as sibling. */
077        protected static final CmsResourceCopyMode MODE_COPY_AS_SIBLING = new CmsResourceCopyMode(2);
078
079        /** Copy mode to preserve siblings during copy. */
080        protected static final CmsResourceCopyMode MODE_COPY_PRESERVE_SIBLING = new CmsResourceCopyMode(3);
081
082        /** Version id required for safe serialization. */
083        private static final long serialVersionUID = 9081630878178799137L;
084
085        /**
086         * Private constructor.<p>
087         *
088         * @param mode the copy mode integer representation
089         */
090        private CmsResourceCopyMode(int mode) {
091
092            super(mode);
093        }
094
095        /**
096         * Returns the copy mode object from the old copy mode integer.<p>
097         *
098         * @param mode the old copy mode integer
099         *
100         * @return the copy mode object
101         */
102        public static CmsResourceCopyMode valueOf(int mode) {
103
104            switch (mode) {
105                case 1:
106                    return CmsResourceCopyMode.MODE_COPY_AS_NEW;
107                case 2:
108                    return CmsResourceCopyMode.MODE_COPY_AS_SIBLING;
109                case 3:
110                default:
111                    return CmsResourceCopyMode.MODE_COPY_PRESERVE_SIBLING;
112            }
113        }
114    }
115
116    /**
117     *  Enumeration class for resource delete modes.<p>
118     */
119    public static final class CmsResourceDeleteMode extends A_CmsModeIntEnumeration {
120
121        /** Signals that siblings of this resource should not be deleted. */
122        protected static final CmsResourceDeleteMode MODE_DELETE_PRESERVE_SIBLINGS = new CmsResourceDeleteMode(1);
123
124        /** Signals that siblings of this resource should be deleted. */
125        protected static final CmsResourceDeleteMode MODE_DELETE_REMOVE_SIBLINGS = new CmsResourceDeleteMode(2);
126
127        /** Version id required for safe serialization. */
128        private static final long serialVersionUID = 2010402524576925865L;
129
130        /**
131         * Private constructor.<p>
132         *
133         * @param mode the delete mode integer representation
134         */
135        private CmsResourceDeleteMode(int mode) {
136
137            super(mode);
138        }
139
140        /**
141         * Returns the delete mode object from the old delete mode integer.<p>
142         *
143         * @param mode the old delete mode integer
144         *
145         * @return the delete mode object
146         */
147        public static CmsResourceDeleteMode valueOf(int mode) {
148
149            switch (mode) {
150                case 1:
151                    return CmsResourceDeleteMode.MODE_DELETE_PRESERVE_SIBLINGS;
152                case 2:
153                default:
154                    return CmsResourceDeleteMode.MODE_DELETE_REMOVE_SIBLINGS;
155            }
156        }
157    }
158
159    /**
160     *  Enumeration class for resource undo changes modes.<p>
161     */
162    public static final class CmsResourceUndoMode extends A_CmsModeIntEnumeration {
163
164        /** Indicates that the undo method will only undo content changes. */
165        public static final CmsResourceUndoMode MODE_UNDO_CONTENT = new CmsResourceUndoMode(1);
166
167        /** Indicates that the undo method will only recursive undo content changes. */
168        public static final CmsResourceUndoMode MODE_UNDO_CONTENT_RECURSIVE = new CmsResourceUndoMode(2);
169
170        /** Indicates that the undo method will undo move operations and content changes. */
171        public static final CmsResourceUndoMode MODE_UNDO_MOVE_CONTENT = new CmsResourceUndoMode(3);
172
173        /** Indicates that the undo method will undo move operations and recursive content changes. */
174        public static final CmsResourceUndoMode MODE_UNDO_MOVE_CONTENT_RECURSIVE = new CmsResourceUndoMode(4);
175
176        /** Version id required for safe serialization. */
177        private static final long serialVersionUID = 3521620626485212068L;
178
179        /**
180         * private constructor.<p>
181         *
182         * @param mode the undo changes mode integer representation
183         */
184        private CmsResourceUndoMode(int mode) {
185
186            super(mode);
187        }
188
189        /**
190         * Gets the undo mode for the given parameters.<p>
191         *
192         * @param move flag for undoing move operations
193         * @param recursive flag for recursive undo
194         *
195         * @return the undo mode
196         */
197        public static CmsResourceUndoMode getUndoMode(boolean move, boolean recursive) {
198
199            if (move) {
200                return recursive
201                ? CmsResourceUndoMode.MODE_UNDO_MOVE_CONTENT_RECURSIVE
202                : CmsResourceUndoMode.MODE_UNDO_MOVE_CONTENT;
203            } else {
204                return recursive
205                ? CmsResourceUndoMode.MODE_UNDO_CONTENT_RECURSIVE
206                : CmsResourceUndoMode.MODE_UNDO_CONTENT;
207            }
208        }
209
210        /**
211         * Returns the undo mode object from the old undo mode integer.<p>
212         *
213         * @param mode the old undo mode integer
214         *
215         * @return the undo mode object
216         */
217        public static CmsResourceUndoMode valueOf(int mode) {
218
219            switch (mode) {
220                case 1:
221                    return CmsResourceUndoMode.MODE_UNDO_CONTENT;
222                case 2:
223                    return CmsResourceUndoMode.MODE_UNDO_CONTENT_RECURSIVE;
224                case 3:
225                    return CmsResourceUndoMode.MODE_UNDO_MOVE_CONTENT;
226                case 4:
227                default:
228                    return CmsResourceUndoMode.MODE_UNDO_MOVE_CONTENT_RECURSIVE;
229            }
230        }
231
232        /**
233         * Returns a mode that includes the move operation with the same semantic as this mode.<p>
234         *
235         * @return a mode that includes the move operation with the same semantic as this mode
236         */
237        public CmsResourceUndoMode includeMove() {
238
239            if (!isUndoMove()) {
240                // keep the same semantic but including move
241                return CmsResourceUndoMode.valueOf(getMode() + 2);
242            }
243            return this;
244        }
245
246        /**
247         * Returns <code>true</code> if this undo operation is recursive.<p>
248         *
249         * @return <code>true</code> if this undo operation is recursive
250         */
251        public boolean isRecursive() {
252
253            return getMode() > CmsResource.UNDO_CONTENT.getMode();
254        }
255
256        /**
257         * Returns <code>true</code> if this undo mode will undo move operations.<p>
258         *
259         * @return <code>true</code> if this undo mode will undo move operations
260         */
261        public boolean isUndoMove() {
262
263            return getMode() > CmsResource.UNDO_CONTENT_RECURSIVE.getMode();
264        }
265
266        /**
267         * @see java.lang.Object#toString()
268         */
269        @Override
270        public String toString() {
271
272            return String.valueOf(getMode());
273        }
274    }
275
276    /** Special folders below which resources should be treated the same as if they were marked as internal. */
277    private static List<String> internalFolders = Arrays.asList(
278        CmsStringUtil.joinPaths(CmsUserIconHelper.USER_IMAGE_FOLDER, "temp"));
279
280    /** Copy mode for copy resources as new resource. */
281    public static final CmsResourceCopyMode COPY_AS_NEW = CmsResourceCopyMode.MODE_COPY_AS_NEW;
282
283    /** Copy mode for copy resources as sibling. */
284    public static final CmsResourceCopyMode COPY_AS_SIBLING = CmsResourceCopyMode.MODE_COPY_AS_SIBLING;
285
286    /** Copy mode to preserve siblings during copy. */
287    public static final CmsResourceCopyMode COPY_PRESERVE_SIBLING = CmsResourceCopyMode.MODE_COPY_PRESERVE_SIBLING;
288
289    /** The default expiration date of a resource, which is: never expires. */
290    public static final long DATE_EXPIRED_DEFAULT = Long.MAX_VALUE;
291
292    /** The default release date of a resource, which is: always released. */
293    public static final long DATE_RELEASED_DEFAULT = 0;
294
295    /** A special date that indicates release and expiration information are to be ignored. */
296    public static final long DATE_RELEASED_EXPIRED_IGNORE = Long.MIN_VALUE;
297
298    /** Signals that siblings of this resource should not be deleted. */
299    public static final CmsResourceDeleteMode DELETE_PRESERVE_SIBLINGS = CmsResourceDeleteMode.MODE_DELETE_PRESERVE_SIBLINGS;
300
301    /** Signals that siblings of this resource should be deleted. */
302    public static final CmsResourceDeleteMode DELETE_REMOVE_SIBLINGS = CmsResourceDeleteMode.MODE_DELETE_REMOVE_SIBLINGS;
303
304    /** Flag to indicate that this is an internal resource, that can't be accessed directly. */
305    public static final int FLAG_INTERNAL = 512;
306
307    /** The resource is linked inside a site folder specified in the OpenCms configuration. */
308    public static final int FLAG_LABELED = 2;
309
310    /** Flag to indicate that this is a temporary resource. */
311    public static final int FLAG_TEMPFILE = 1024;
312
313    /** The name constraints when generating new resources. */
314    public static final String NAME_CONSTRAINTS = "-._~$";
315
316    /** Indicates if a resource has been changed in the offline version when compared to the online version. */
317    public static final CmsResourceState STATE_CHANGED = CmsResourceState.STATE_CHANGED;
318
319    /** Indicates if a resource has been deleted in the offline version when compared to the online version. */
320    public static final CmsResourceState STATE_DELETED = CmsResourceState.STATE_DELETED;
321
322    /**
323     * Special state value that indicates the current state must be kept on a resource,
324     * this value must never be written to the database.
325     */
326    public static final CmsResourceState STATE_KEEP = CmsResourceState.STATE_KEEP;
327
328    /** Indicates if a resource is new in the offline version when compared to the online version. */
329    public static final CmsResourceState STATE_NEW = CmsResourceState.STATE_NEW;
330
331    /** Indicates if a resource is unchanged in the offline version when compared to the online version. */
332    public static final CmsResourceState STATE_UNCHANGED = CmsResourceState.STATE_UNCHANGED;
333
334    /**
335     * Prefix for temporary files in the VFS.
336     *
337     * @see #isTemporaryFile()
338     * @see #isTemporaryFileName(String)
339     */
340    public static final String TEMP_FILE_PREFIX = CmsDriverManager.TEMP_FILE_PREFIX;
341
342    /** Flag for leaving a date unchanged during a touch operation. */
343    public static final long TOUCH_DATE_UNCHANGED = -1;
344
345    /** Indicates that the undo method will only undo content changes. */
346    public static final CmsResourceUndoMode UNDO_CONTENT = CmsResourceUndoMode.MODE_UNDO_CONTENT;
347
348    /** Indicates that the undo method will only recursive undo content changes. */
349    public static final CmsResourceUndoMode UNDO_CONTENT_RECURSIVE = CmsResourceUndoMode.MODE_UNDO_CONTENT_RECURSIVE;
350
351    /** Indicates that the undo method will undo move operations and content changes. */
352    public static final CmsResourceUndoMode UNDO_MOVE_CONTENT = CmsResourceUndoMode.MODE_UNDO_MOVE_CONTENT;
353
354    /** Indicates that the undo method will undo move operations and recursive content changes. */
355    public static final CmsResourceUndoMode UNDO_MOVE_CONTENT_RECURSIVE = CmsResourceUndoMode.MODE_UNDO_MOVE_CONTENT_RECURSIVE;
356
357    /** The vfs path of the sites master folder. */
358    public static final String VFS_FOLDER_SITES = "/sites";
359
360    /** The vfs path of the system folder. */
361    public static final String VFS_FOLDER_SYSTEM = "/system";
362
363    /** Serial version UID required for safe serialization. */
364    private static final long serialVersionUID = 257325098790850498L;
365
366    /** The date of the last modification of the content of this resource. */
367    protected long m_dateContent = System.currentTimeMillis();
368
369    /** The size of the content. */
370    protected int m_length;
371
372    /** The creation date of this resource. */
373    private long m_dateCreated;
374
375    /** The expiration date of this resource. */
376    private long m_dateExpired;
377
378    /** The date of the last modification of this resource. */
379    private long m_dateLastModified;
380
381    /** The release date of this resource. */
382    private long m_dateReleased;
383
384    /** The flags of this resource. */
385    private int m_flags;
386
387    /** Indicates if this resource is a folder or not. */
388    private boolean m_isFolder;
389
390    /** Boolean flag whether the timestamp of this resource was modified by a touch command. */
391    private boolean m_isTouched;
392
393    /** The project id where this resource has been last modified in. */
394    private CmsUUID m_projectLastModified;
395
396    /** The id of the resource database record. */
397    private CmsUUID m_resourceId;
398
399    /** The name of a resource with it's full path from the root folder including the current site root. */
400    private String m_rootPath;
401
402    /** The number of links that point to this resource. */
403    private int m_siblingCount;
404
405    /** The state of this resource. */
406    private CmsResourceState m_state;
407
408    /** The id of the structure database record. */
409    private CmsUUID m_structureId;
410
411    /** The resource type id of this resource. */
412    private int m_typeId;
413
414    /** The id of the user who created this resource. */
415    private CmsUUID m_userCreated;
416
417    /** The id of the user who modified this resource last. */
418    private CmsUUID m_userLastModified;
419
420    /** The version number of this resource. */
421    private int m_version;
422
423    /**
424     * Creates a new CmsRecource object.<p>
425     *
426     * @param structureId the id of this resources structure record
427     * @param resourceId the id of this resources resource record
428     * @param rootPath the root path to the resource
429     * @param type the type of this resource
430     * @param flags the flags of this resource
431     * @param projectId the project id this resource was last modified in
432     * @param state the state of this resource
433     * @param dateCreated the creation date of this resource
434     * @param userCreated the id of the user who created this resource
435     * @param dateLastModified the date of the last modification of this resource
436     * @param userLastModified the id of the user who did the last modification of this resource
437     * @param dateReleased the release date of this resource
438     * @param dateExpired the expiration date of this resource
439     * @param linkCount the count of all siblings of this resource
440     * @param size the size of the file content of this resource
441     * @param dateContent the date of the last modification of the content of this resource
442     * @param version the version number of this resource
443     */
444    public CmsResource(
445        CmsUUID structureId,
446        CmsUUID resourceId,
447        String rootPath,
448        I_CmsResourceType type,
449        int flags,
450        CmsUUID projectId,
451        CmsResourceState state,
452        long dateCreated,
453        CmsUUID userCreated,
454        long dateLastModified,
455        CmsUUID userLastModified,
456        long dateReleased,
457        long dateExpired,
458        int linkCount,
459        int size,
460        long dateContent,
461        int version) {
462
463        this(
464            structureId,
465            resourceId,
466            rootPath,
467            type.getTypeId(),
468            type.isFolder(),
469            flags,
470            projectId,
471            state,
472            dateCreated,
473            userCreated,
474            dateLastModified,
475            userLastModified,
476            dateReleased,
477            dateExpired,
478            linkCount,
479            size,
480            dateContent,
481            version);
482    }
483
484    /**
485     * Creates a new CmsRecource object.<p>
486     *
487     * @param structureId the id of this resources structure record
488     * @param resourceId the id of this resources resource record
489     * @param rootPath the root path to the resource
490     * @param type the type of this resource
491     * @param isFolder must be true if the resource is a folder, or false if it is a file
492     * @param flags the flags of this resource
493     * @param projectId the project id this resource was last modified in
494     * @param state the state of this resource
495     * @param dateCreated the creation date of this resource
496     * @param userCreated the id of the user who created this resource
497     * @param dateLastModified the date of the last modification of this resource
498     * @param userLastModified the id of the user who did the last modification of this resource
499     * @param dateReleased the release date of this resource
500     * @param dateExpired the expiration date of this resource
501     * @param linkCount the count of all siblings of this resource
502     * @param size the size of the file content of this resource
503     * @param dateContent the date of the last modification of the content of this resource
504     * @param version the version number of this resource
505     */
506    public CmsResource(
507        CmsUUID structureId,
508        CmsUUID resourceId,
509        String rootPath,
510        int type,
511        boolean isFolder,
512        int flags,
513        CmsUUID projectId,
514        CmsResourceState state,
515        long dateCreated,
516        CmsUUID userCreated,
517        long dateLastModified,
518        CmsUUID userLastModified,
519        long dateReleased,
520        long dateExpired,
521        int linkCount,
522        int size,
523        long dateContent,
524        int version) {
525
526        m_structureId = structureId;
527        m_resourceId = resourceId;
528        m_rootPath = rootPath;
529        m_typeId = type;
530        m_isFolder = isFolder;
531        m_flags = flags;
532        m_projectLastModified = projectId;
533        m_state = state;
534        m_dateCreated = dateCreated;
535        m_userCreated = userCreated;
536        m_dateLastModified = dateLastModified;
537        m_userLastModified = userLastModified;
538        m_dateReleased = dateReleased;
539        m_dateExpired = dateExpired;
540        m_siblingCount = linkCount;
541        m_length = size;
542        m_dateContent = dateContent;
543        m_version = version;
544        m_isTouched = false;
545    }
546
547    /**
548     * Checks if the provided resource name is a valid resource name,
549     * that is contains only valid characters.<p>
550     *
551     * A resource name can only be composed of digits,
552     * standard ASCII letters and the symbols defined in {@link #NAME_CONSTRAINTS}.
553     * A resource name must also not contain only dots.<p>
554     *
555     * @param name the resource name to check
556     *
557     * @throws CmsIllegalArgumentException if the given resource name is not valid
558     */
559    public static void checkResourceName(String name) throws CmsIllegalArgumentException {
560
561        if (CmsStringUtil.isEmptyOrWhitespaceOnly(name)) {
562            throw new CmsIllegalArgumentException(
563                Messages.get().container(Messages.ERR_BAD_RESOURCENAME_EMPTY_0, name));
564        }
565
566        CmsStringUtil.checkName(name, NAME_CONSTRAINTS, Messages.ERR_BAD_RESOURCENAME_4, Messages.get());
567
568        // check for filenames that have only dots (which will cause issues in the static export)
569        boolean onlydots = true;
570        // this must be done only for the last name (not for parent folders)
571        String lastName = CmsResource.getName(name);
572        int l = lastName.length();
573        for (int i = 0; i < l; i++) {
574            char c = lastName.charAt(i);
575            if ((c != '.') && (c != '/')) {
576                onlydots = false;
577            }
578        }
579        if (onlydots) {
580            throw new CmsIllegalArgumentException(
581                Messages.get().container(Messages.ERR_BAD_RESOURCENAME_DOTS_1, lastName));
582        }
583    }
584
585    /**
586     * Returns the resource name extension if present.<p>
587     * The extension will be always lower case.<p>
588     *
589     * @param resourceName the resource name or path
590     *
591     * @return the extension or <code>null</code> if not available
592     */
593    public static String getExtension(String resourceName) {
594
595        String result = null;
596        if (!resourceName.endsWith("/")) {
597            // folders don't have any extension
598            resourceName = CmsResource.getName(resourceName);
599            int index = resourceName.lastIndexOf(".") + 1;
600            if ((index > 0) && (index < resourceName.length())) {
601                result = resourceName.substring(index).toLowerCase();
602            }
603        }
604
605        return result;
606    }
607
608    /**
609     * Returns the folder path of the resource with the given name.<p>
610     *
611     * If the resource name denotes a folder (that is ends with a "/"), the complete path of the folder
612     * is returned (not the parent folder path).<p>
613     *
614     * This is achieved by just cutting of everything behind the last occurrence of a "/" character
615     * in the String, no check if performed if the resource exists or not in the VFS,
616     * only resources that end with a "/" are considered to be folders.
617     *
618     * Example: Returns <code>/system/def/</code> for the
619     * resource <code>/system/def/file.html</code> and
620     * <code>/system/def/</code> for the (folder) resource <code>/system/def/</code>.
621     *
622     * @param resource the name of a resource
623     * @return the folder of the given resource
624     */
625    public static String getFolderPath(String resource) {
626
627        return resource.substring(0, resource.lastIndexOf('/') + 1);
628    }
629
630    /**
631     * Returns the name of a resource without the path information.<p>
632     *
633     * The resource name of a file is the name of the file.
634     * The resource name of a folder is the folder name with trailing "/".
635     * The resource name of the root folder is <code>/</code>.<p>
636     *
637     * Example: <code>/system/workplace/</code> has the resource name <code>workplace/</code>.
638     *
639     * @param resource the resource to get the name for
640     * @return the name of a resource without the path information
641     */
642    public static String getName(String resource) {
643
644        if ("/".equals(resource)) {
645            return "/";
646        }
647        // remove the last char, for a folder this will be "/", for a file it does not matter
648        String parent = (resource.substring(0, resource.length() - 1));
649        // now as the name does not end with "/", check for the last "/" which is the parent folder name
650        return resource.substring(parent.lastIndexOf('/') + 1);
651    }
652
653    /**
654     * Returns the absolute parent folder name of a resource.<p>
655     *
656     * The parent resource of a file is the folder of the file.
657     * The parent resource of a folder is the parent folder.
658     * The parent resource of the root folder is <code>null</code>.<p>
659     *
660     * Example: <code>/system/workplace/</code> has the parent <code>/system/</code>.
661     *
662     * @param resource the resource to find the parent folder for
663     * @return the calculated parent absolute folder path, or <code>null</code> for the root folder
664     */
665    public static String getParentFolder(String resource) {
666
667        if (CmsStringUtil.isEmptyOrWhitespaceOnly(resource) || "/".equals(resource)) {
668            return null;
669        }
670        // remove the last char, for a folder this will be "/", for a file it does not matter
671        String parent = (resource.substring(0, resource.length() - 1));
672        // now as the name does not end with "/", check for the last "/" which is the parent folder name
673        return parent.substring(0, parent.lastIndexOf('/') + 1);
674    }
675
676    /**
677     * Returns the directory level of a resource.<p>
678     *
679     * The root folder "/" has level 0,
680     * a folder "/foo/" would have level 1,
681     * a folfer "/foo/bar/" level 2 etc.<p>
682     *
683     * @param resource the resource to determine the directory level for
684     * @return the directory level of a resource
685     */
686    public static int getPathLevel(String resource) {
687
688        int level = -1;
689        int pos = 0;
690        while (resource.indexOf('/', pos) >= 0) {
691            pos = resource.indexOf('/', pos) + 1;
692            level++;
693        }
694        return level;
695    }
696
697    /**
698     * Returns the name of a parent folder of the given resource,
699     * that is either minus levels up
700     * from the current folder, or that is plus levels down from the
701     * root folder.<p>
702     *
703     * @param resource the name of a resource
704     * @param level of levels to walk up or down
705     * @return the name of a parent folder of the given resource
706     */
707    public static String getPathPart(String resource, int level) {
708
709        resource = getFolderPath(resource);
710        String result = null;
711        int pos = 0, count = 0;
712        if (level >= 0) {
713            // Walk down from the root folder /
714            while ((count < level) && (pos > -1)) {
715                count++;
716                pos = resource.indexOf('/', pos + 1);
717            }
718        } else {
719            // Walk up from the current folder
720            pos = resource.length();
721            while ((count > level) && (pos > -1)) {
722                count--;
723                pos = resource.lastIndexOf('/', pos - 1);
724            }
725        }
726        if (pos > -1) {
727            // To many levels walked
728            result = resource.substring(0, pos + 1);
729        } else {
730            // Add trailing slash
731            result = (level < 0) ? "/" : resource;
732        }
733        return result;
734    }
735
736    /**
737     * Returns true if the resource name certainly denotes a folder, that is ends with a "/".<p>
738     *
739     * @param resource the resource to check
740     * @return true if the resource name certainly denotes a folder, that is ends with a "/"
741     */
742    public static boolean isFolder(String resource) {
743
744        return CmsStringUtil.isNotEmpty(resource) && (resource.charAt(resource.length() - 1) == '/');
745    }
746
747    /**
748     * Returns <code>true</code> if the given resource path points to a temporary file name.<p>
749     *
750     * A resource name is considered a temporary file name if the name of the file
751     * (without parent folders) starts with the prefix char <code>'~'</code> (tilde).
752     * Existing parent folder elements are removed from the path before the file name is checked.<p>
753     *
754     * @param path the resource path to check
755     *
756     * @return <code>true</code> if the given resource name is a temporary file name
757     *
758     * @see #isTemporaryFile()
759     */
760    public static boolean isTemporaryFileName(String path) {
761
762        return (path != null) && getName(path).startsWith(TEMP_FILE_PREFIX);
763    }
764
765    /**
766     * Returns a clone of this Objects instance.<p>
767     *
768     * @return a clone of this instance
769     */
770    @Override
771    public Object clone() {
772
773        return getCopy();
774    }
775
776    /**
777     * Uses the resource root path to compare two resources.<p>
778     *
779     * Please note a number of additional comparators for resources exists as members of this class.<p>
780     *
781     * @see java.lang.Comparable#compareTo(java.lang.Object)
782     *
783     * @see #COMPARE_DATE_RELEASED
784     * @see #COMPARE_ROOT_PATH
785     * @see #COMPARE_ROOT_PATH_IGNORE_CASE
786     * @see #COMPARE_ROOT_PATH_IGNORE_CASE_FOLDERS_FIRST
787     */
788    public int compareTo(I_CmsResource obj) {
789
790        if (obj == this) {
791            return 0;
792        }
793        return m_rootPath.compareTo(obj.getRootPath());
794    }
795
796    /**
797     * Two resources are considered equal in case their structure id is equal.<p>
798     *
799     * @see java.lang.Object#equals(java.lang.Object)
800     */
801    @Override
802    public boolean equals(Object obj) {
803
804        if (obj == null) {
805            return false;
806        }
807
808        if (obj == this) {
809            return true;
810        }
811        if (obj instanceof CmsResource) {
812            return ((CmsResource)obj).m_structureId.equals(m_structureId);
813        }
814        return false;
815    }
816
817    /**
818     * Creates a copy of this resource.<p>
819     *
820     * This is useful in case you want to create a copy of a resource and
821     * really make sure won't get a {@link CmsFile} or {@link CmsFolder}, which may happen
822     * if you just call {@link #clone()}.<p>
823     *
824     * @return a copy of this resource
825     */
826    public CmsResource getCopy() {
827
828        CmsResource result = new CmsResource(
829            m_structureId,
830            m_resourceId,
831            m_rootPath,
832            m_typeId,
833            m_isFolder,
834            m_flags,
835            m_projectLastModified,
836            m_state,
837            m_dateCreated,
838            m_userCreated,
839            m_dateLastModified,
840            m_userLastModified,
841            m_dateReleased,
842            m_dateExpired,
843            m_siblingCount,
844            m_length,
845            m_dateContent,
846            m_version);
847
848        if (isTouched()) {
849            result.setDateLastModified(m_dateLastModified);
850        }
851
852        return result;
853    }
854
855    /**
856     * Returns the date of the last modification of the content of this resource.<p>
857     *
858     * This applies only to resources of type {@link CmsFile}, since a {@link CmsFolder} has no content.
859     * In case of a folder, <code>-1</code> is always returned as content date.<p>
860     *
861     * Any modification of a resource, including changes to the resource properties,
862     * will increase the "date of last modification" which is returned by {@link #getDateLastModified()}.
863     * The "date of the content" as returned by this method only changes when the
864     * file content as returned by {@link CmsFile#getContents()} is changed.<p>
865     *
866     * @return the date of the last modification of the content of this resource
867     *
868     * @since 7.0.0
869     */
870    public long getDateContent() {
871
872        return m_dateContent;
873    }
874
875    /**
876     * Returns the date of the creation of this resource.<p>
877     *
878     * @return the date of the creation of this resource
879     */
880    public long getDateCreated() {
881
882        return m_dateCreated;
883    }
884
885    /**
886     * Returns the expiration date this resource.<p>
887     *
888     * If the expiration date has not been set, {@link #DATE_EXPIRED_DEFAULT} is returned.
889     * This means: The resource does never expire.<p>
890     *
891     * @return the expiration date of this resource
892     */
893    public long getDateExpired() {
894
895        return m_dateExpired;
896    }
897
898    /**
899     * Returns the date of the last modification of this resource.<p>
900     *
901     * @return the date of the last modification of this resource
902     */
903    public long getDateLastModified() {
904
905        return m_dateLastModified;
906    }
907
908    /**
909     * Returns the release date this resource.<p>
910     *
911     * If the release date has not been set, {@link #DATE_RELEASED_DEFAULT} is returned.
912     * This means: The resource has always been released.<p>
913     *
914     * @return the release date of this resource
915     */
916    public long getDateReleased() {
917
918        return m_dateReleased;
919    }
920
921    /**
922     * Returns the flags of this resource.<p>
923     *
924     * @return the flags of this resource
925     *
926     * @see #setFlags(int) for an explanation of the resource flags
927     */
928    public int getFlags() {
929
930        return m_flags;
931    }
932
933    /**
934     * Returns the content length of this resource.<p>
935     *
936     * If the resource is a file, then this is the byte size of the file content.
937     * If the resource is a folder, then the size is always -1.<p>
938     *
939     * @return the content length of the content
940     */
941    public int getLength() {
942
943        // make sure folders always have a -1 size
944        return m_isFolder ? -1 : m_length;
945    }
946
947    /**
948     * Returns the file name of this resource without parent folders, for example <code>index.html</code>.<p>
949     *
950     * @return the file name of this resource without parent folders
951     */
952    public String getName() {
953
954        String name = getName(m_rootPath);
955        if (name.charAt(name.length() - 1) == '/') {
956            return name.substring(0, name.length() - 1);
957        } else {
958            return name;
959        }
960    }
961
962    /**
963     * Returns the id of the {@link CmsProject} where this resource has been last modified.<p>
964     *
965     * @return the id of the {@link CmsProject} where this resource has been last modified, or <code>null</code>
966     */
967    public CmsUUID getProjectLastModified() {
968
969        return m_projectLastModified;
970    }
971
972    /**
973     * Returns the id of the database content record of this resource.<p>
974     *
975     * @return the id of the database content record of this resource
976     */
977    public CmsUUID getResourceId() {
978
979        return m_resourceId;
980    }
981
982    /**
983     * Returns the name of this resource with it's full path from the top level root folder,
984     * for example <code>/sites/default/myfolder/index.html</code>.<p>
985     *
986     * In a presentation level application usually the current site root must be
987     * cut of from the root path. Use {@link CmsObject#getSitePath(CmsResource)}
988     * to get the "absolute" path of a resource in the current site.<p>
989     *
990     * @return the name of this resource with it's full path from the top level root folder
991     *
992     * @see CmsObject#getSitePath(CmsResource)
993     * @see CmsRequestContext#getSitePath(CmsResource)
994     * @see CmsRequestContext#removeSiteRoot(String)
995     */
996    public String getRootPath() {
997
998        return m_rootPath;
999    }
1000
1001    /**
1002     * Returns the number of siblings of this resource, also counting this resource.<p>
1003     *
1004     * If a resource has no sibling, the total sibling count for this resource is <code>1</code>,
1005     * if a resource has <code>n</code> siblings, the sibling count is <code>n + 1</code>.<p>
1006     *
1007     * @return the number of siblings of this resource, also counting this resource
1008     */
1009    public int getSiblingCount() {
1010
1011        return m_siblingCount;
1012    }
1013
1014    /**
1015     * Returns the state of this resource.<p>
1016     *
1017     * This may be {@link CmsResource#STATE_UNCHANGED},
1018     * {@link CmsResource#STATE_CHANGED}, {@link CmsResource#STATE_NEW}
1019     * or {@link CmsResource#STATE_DELETED}.<p>
1020     *
1021     * @return the state of this resource
1022     */
1023    public CmsResourceState getState() {
1024
1025        return m_state;
1026    }
1027
1028    /**
1029     * Returns the id of the database structure record of this resource.<p>
1030     *
1031     * @return the id of the database structure record of this resource
1032     */
1033    public CmsUUID getStructureId() {
1034
1035        return m_structureId;
1036    }
1037
1038    /**
1039     * Returns the resource type id for this resource.<p>
1040     *
1041     * @return the resource type id of this resource
1042     */
1043    public int getTypeId() {
1044
1045        return m_typeId;
1046    }
1047
1048    /**
1049     * Returns the user id of the {@link CmsUser} who created this resource.<p>
1050     *
1051     * @return the user id of the {@link CmsUser} who created this resource
1052     */
1053    public CmsUUID getUserCreated() {
1054
1055        return m_userCreated;
1056    }
1057
1058    /**
1059     * Returns the id of the {@link CmsUser} who made the last modification on this resource.<p>
1060     *
1061     * @return the id of the {@link CmsUser} who made the last modification on this resource
1062     */
1063    public CmsUUID getUserLastModified() {
1064
1065        return m_userLastModified;
1066    }
1067
1068    /**
1069     * Returns the current version number of this resource.<p>
1070     *
1071     * @return the current version number of this resource
1072     */
1073    public int getVersion() {
1074
1075        return m_version;
1076    }
1077
1078    /**
1079     * @see java.lang.Object#hashCode()
1080     */
1081    @Override
1082    public int hashCode() {
1083
1084        if (m_structureId != null) {
1085            return m_structureId.hashCode();
1086        }
1087
1088        return CmsUUID.getNullUUID().hashCode();
1089    }
1090
1091    /**
1092     * Returns <code>true</code> if this resource is expired at the given time according to the
1093     * information stored in {@link #getDateExpired()}.<p>
1094     *
1095     * @param time the time to check the expiration date against
1096     *
1097     * @return <code>true</code> if this resource is expired at the given time
1098     *
1099     * @see #isReleased(long)
1100     * @see #isReleasedAndNotExpired(long)
1101     * @see #DATE_RELEASED_EXPIRED_IGNORE
1102     * @see CmsResource#getDateReleased()
1103     * @see CmsRequestContext#getRequestTime()
1104     */
1105    public boolean isExpired(long time) {
1106
1107        return (time > m_dateExpired) && (time != DATE_RELEASED_EXPIRED_IGNORE);
1108    }
1109
1110    /**
1111     * Returns <code>true</code> if the resource is a {@link CmsFile}, that is not a {@link CmsFolder}.<p>
1112     *
1113     * @return true if this resource is a file, false otherwise
1114     */
1115    public boolean isFile() {
1116
1117        return !m_isFolder;
1118    }
1119
1120    /**
1121     * Returns <code>true</code> if the resource is a {@link CmsFolder}, that is not a {@link CmsFile}.<p>
1122     *
1123     * @return true if this resource is a folder, false otherwise
1124     */
1125    public boolean isFolder() {
1126
1127        return m_isFolder;
1128    }
1129
1130    /**
1131     * Returns <code>true</code> if the resource is marked as internal.<p>
1132     *
1133     * An internal resource can be read by the OpenCms API, but it can not be delivered
1134     * by a direct request from an outside user.<p>
1135     *
1136     * For example if the resource <code>/internal.xml</code>
1137     * has been set as marked as internal, this resource can not be requested by an HTTP request,
1138     * so when a user enters <code>http:/www.myserver.com/opencms/opencms/internal.xml</code> in the browser
1139     * this will generate a {@link CmsVfsResourceNotFoundException}.<p>
1140     *
1141     * This state is stored as bit 1 in the resource flags.<p>
1142     *
1143     * @return <code>true</code> if the resource is internal
1144     */
1145    public boolean isInternal() {
1146
1147        return ((m_flags & FLAG_INTERNAL) > 0);
1148    }
1149
1150    /**
1151     * Checks if either the resource's 'internal' flag is set, or if it's below a list of forbidden folders.
1152     *
1153     * @return true if the resource should be treated as internal
1154     */
1155    public boolean isInternalOrInInternalFolder() {
1156
1157        if (isInternal()) {
1158            return true;
1159        }
1160        for (String forbiddenFolder : internalFolders) {
1161            if (CmsStringUtil.isPrefixPath(forbiddenFolder, getRootPath())) {
1162                return true;
1163            }
1164        }
1165        return false;
1166    }
1167
1168    /**
1169     * Returns <code>true</code> if the resource has to be labeled with a special icon in the explorer view.<p>
1170     *
1171     * This state is stored as bit 2 in the resource flags.<p>
1172     *
1173     * @return <code>true</code> if the resource has to be labeled in the explorer view
1174     */
1175    public boolean isLabeled() {
1176
1177        return ((m_flags & CmsResource.FLAG_LABELED) > 0);
1178    }
1179
1180    /**
1181     * Returns <code>true</code> if this resource is released at the given time according to the
1182     * information stored in {@link #getDateReleased()}.<p>
1183     *
1184     * @param time the time to check the release date against
1185     *
1186     * @return <code>true</code> if this resource is released at the given time
1187     *
1188     * @see #isExpired(long)
1189     * @see #isReleasedAndNotExpired(long)
1190     * @see #DATE_RELEASED_EXPIRED_IGNORE
1191     * @see CmsResource#getDateReleased()
1192     * @see CmsRequestContext#getRequestTime()
1193     */
1194    public boolean isReleased(long time) {
1195
1196        return (time > m_dateReleased) || (time == DATE_RELEASED_EXPIRED_IGNORE);
1197    }
1198
1199    /**
1200     * Returns <code>true</code> if this resource is valid at the given time according to the
1201     * information stored in {@link #getDateReleased()} and {@link #getDateExpired()}.<p>
1202     *
1203     * A resource is valid if it is released and not yet expired.<p>
1204     *
1205     * @param time the time to check the release and expiration date against
1206     *
1207     * @return <code>true</code> if this resource is valid at the given time
1208     *
1209     * @see #isExpired(long)
1210     * @see #isReleased(long)
1211     * @see #DATE_RELEASED_EXPIRED_IGNORE
1212     * @see CmsResource#getDateReleased()
1213     * @see CmsRequestContext#getRequestTime()
1214     */
1215    public boolean isReleasedAndNotExpired(long time) {
1216
1217        return ((time < m_dateExpired) && (time > m_dateReleased)) || (time == DATE_RELEASED_EXPIRED_IGNORE);
1218    }
1219
1220    /**
1221     * Returns <code>true</code> if this resource is a temporary file.<p>
1222     *
1223     * A resource is considered a temporary file it is a file where the
1224     * {@link CmsResource#FLAG_TEMPFILE} flag has been set, or if the file name (without parent folders)
1225     * starts with the prefix char <code>'~'</code> (tilde).<p>
1226     *
1227     * @return <code>true</code> if the given resource name is a temporary file
1228     *
1229     * @see #isTemporaryFileName(String)
1230     */
1231    public boolean isTemporaryFile() {
1232
1233        return isFile() && (((getFlags() & CmsResource.FLAG_TEMPFILE) > 0) || isTemporaryFileName(getName()));
1234    }
1235
1236    /**
1237     * Returns <code>true</code> if this resource was touched.<p>
1238     *
1239     * @return <code>true</code> if this resource was touched
1240     */
1241    public boolean isTouched() {
1242
1243        return m_isTouched;
1244    }
1245
1246    /**
1247     * Sets the expiration date this resource.<p>
1248     *
1249     * @param time the expiration date to set
1250     */
1251    public void setDateExpired(long time) {
1252
1253        m_dateExpired = time;
1254    }
1255
1256    /**
1257     * Sets the date of the last modification of this resource.<p>
1258     *
1259     * @param time the last modification date to set
1260     */
1261    public void setDateLastModified(long time) {
1262
1263        m_isTouched = true;
1264        m_dateLastModified = time;
1265    }
1266
1267    /**
1268     * Sets the release date this resource.<p>
1269     *
1270     * @param time the release date to set
1271     */
1272    public void setDateReleased(long time) {
1273
1274        m_dateReleased = time;
1275    }
1276
1277    /**
1278     * Sets the flags of this resource.<p>
1279     *
1280     * The resource flags integer is used as bit set that contains special information about the resource.
1281     * The following methods internally use the resource flags:<ul>
1282     * <li>{@link #isInternal()}
1283     * <li>{@link #isLabeled()}
1284     * </ul>
1285     *
1286     * @param flags the flags value to set
1287     */
1288    public void setFlags(int flags) {
1289
1290        m_flags = flags;
1291    }
1292
1293    /**
1294     * Sets or clears the internal flag.<p>
1295     *
1296     * @param internal true if the internal flag should be set, false if it should be cleared
1297     */
1298    public void setInternal(boolean internal) {
1299
1300        m_flags = (m_flags & ~FLAG_INTERNAL) | (internal ? FLAG_INTERNAL : 0);
1301    }
1302
1303    /**
1304     * Sets the state of this resource.<p>
1305     *
1306     * @param state the state to set
1307     */
1308    public void setState(CmsResourceState state) {
1309
1310        m_state = state;
1311    }
1312
1313    /**
1314     * Sets the type of this resource.<p>
1315     *
1316     * @param type the type to set
1317     */
1318    public void setType(int type) {
1319
1320        m_typeId = type;
1321    }
1322
1323    /**
1324     * Sets the user id of the user who changed this resource.<p>
1325     *
1326     * @param resourceLastModifiedByUserId the user id of the user who changed the resource
1327     */
1328    public void setUserLastModified(CmsUUID resourceLastModifiedByUserId) {
1329
1330        m_userLastModified = resourceLastModifiedByUserId;
1331    }
1332
1333    /**
1334     * @see java.lang.Object#toString()
1335     */
1336    @Override
1337    public String toString() {
1338
1339        StringBuffer result = new StringBuffer();
1340
1341        result.append("[");
1342        result.append(this.getClass().getName());
1343        result.append(", path: ");
1344        result.append(m_rootPath);
1345        result.append(", structure id ");
1346        result.append(m_structureId);
1347        result.append(", resource id: ");
1348        result.append(m_resourceId);
1349        result.append(", type id: ");
1350        result.append(m_typeId);
1351        result.append(", folder: ");
1352        result.append(m_isFolder);
1353        result.append(", flags: ");
1354        result.append(m_flags);
1355        result.append(", project: ");
1356        result.append(m_projectLastModified);
1357        result.append(", state: ");
1358        result.append(m_state);
1359        result.append(", date created: ");
1360        result.append(new java.util.Date(m_dateCreated));
1361        result.append(", user created: ");
1362        result.append(m_userCreated);
1363        result.append(", date lastmodified: ");
1364        result.append(new java.util.Date(m_dateLastModified));
1365        result.append(", user lastmodified: ");
1366        result.append(m_userLastModified);
1367        result.append(", date released: ");
1368        result.append(new java.util.Date(m_dateReleased));
1369        result.append(", date expired: ");
1370        result.append(new java.util.Date(m_dateExpired));
1371        result.append(", date content: ");
1372        result.append(new java.util.Date(m_dateContent));
1373        result.append(", size: ");
1374        result.append(m_length);
1375        result.append(", sibling count: ");
1376        result.append(m_siblingCount);
1377        result.append(", version: ");
1378        result.append(m_version);
1379        result.append("]");
1380
1381        return result.toString();
1382    }
1383}