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