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}