001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (https://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: https://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: https://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.wrapper; 029 030import org.opencms.file.CmsFile; 031import org.opencms.file.CmsObject; 032import org.opencms.file.CmsProperty; 033import org.opencms.file.CmsResource; 034import org.opencms.file.CmsResource.CmsResourceDeleteMode; 035import org.opencms.file.CmsResourceFilter; 036import org.opencms.file.CmsVfsResourceAlreadyExistsException; 037import org.opencms.file.CmsVfsResourceNotFoundException; 038import org.opencms.lock.CmsLock; 039import org.opencms.main.CmsException; 040import org.opencms.main.CmsIllegalArgumentException; 041import org.opencms.main.CmsLog; 042import org.opencms.util.CmsUUID; 043 044import java.util.ArrayList; 045import java.util.Date; 046import java.util.Iterator; 047import java.util.List; 048 049import org.apache.commons.logging.Log; 050 051/** 052 * Adds a folder in every existing folder with the name "__properties" which 053 * contains property files for every resource in the existing folder.<p> 054 * 055 * Empty folders don't have the property folder visible.<p> 056 * 057 * The names of the property files are the same as the resource they belong to 058 * with the extension "properties". To keep the correct sorting the names of 059 * folders gets additionaly the prefix "__" to keep them at the beginning of the 060 * list.<p> 061 * 062 * When creating new folders, the property folder gets visible after a time period 063 * of 60 seconds. For new resources the property file appears after that period too. 064 * In this time period it is possible to create the property folder and the property 065 * files manually. The properties in the created property files will be set at the 066 * resource they belong to.<p> 067 * 068 * @since 6.5.6 069 */ 070public class CmsResourceWrapperPropertyFile extends A_CmsResourceWrapper { 071 072 /** The logger instance for this class. */ 073 private static final Log LOG = CmsLog.getLog(CmsResourceWrapperPropertyFile.class); 074 075 /** The prefix for folders to keep correct sorting. */ 076 private static final String FOLDER_PREFIX = "__"; 077 078 /** The name to use for the folder where all property files are listed in. */ 079 private static final String PROPERTY_DIR = "__properties"; 080 081 /** The time in seconds to wait till the properties (and the property folder) are visible. */ 082 private static final int TIME_DELAY = 60; 083 084 /** Table with the states of the virtual files. */ 085 private static final List<String> TMP_FILE_TABLE = new ArrayList<String>(); 086 087 /** 088 * @see org.opencms.file.wrapper.A_CmsResourceWrapper#addResourcesToFolder(CmsObject, String, CmsResourceFilter) 089 */ 090 @Override 091 public List<CmsResource> addResourcesToFolder(CmsObject cms, String resourcename, CmsResourceFilter filter) 092 throws CmsException { 093 094 String path = resourcename; 095 if (!path.endsWith("/")) { 096 path += "/"; 097 } 098 099 if (path.endsWith(PROPERTY_DIR + "/")) { 100 101 String parent = CmsResource.getParentFolder(path); 102 List<CmsResource> ret = new ArrayList<CmsResource>(); 103 104 // Iterate through all existing resources 105 List<CmsResource> resources = cms.getResourcesInFolder(parent, filter); 106 Iterator<CmsResource> iter = resources.iterator(); 107 while (iter.hasNext()) { 108 CmsResource res = iter.next(); 109 110 // check "existance" of resource 111 if (existsResource(res)) { 112 113 // add the generated property file 114 ret.add(CmsResourceWrapperUtils.createPropertyFile(cms, res, getPropertyFileName(res))); 115 } 116 } 117 118 return ret; 119 } else { 120 121 try { 122 CmsResource folder = cms.readResource(resourcename); 123 if (folder.isFolder()) { 124 125 // check if folder is empty 126 if (!cms.getResourcesInFolder(resourcename, CmsResourceFilter.DEFAULT).isEmpty()) { 127 128 // check "existance" of folder 129 if (existsResource(folder)) { 130 List<CmsResource> ret = new ArrayList<CmsResource>(); 131 132 CmsWrappedResource wrap = new CmsWrappedResource(folder); 133 wrap.setRootPath(folder.getRootPath() + PROPERTY_DIR + "/"); 134 135 ret.add(wrap.getResource()); 136 return ret; 137 } 138 } 139 } 140 } catch (CmsVfsResourceNotFoundException ex) { 141 return null; 142 } 143 } 144 145 return null; 146 } 147 148 /** 149 * @see org.opencms.file.wrapper.A_CmsResourceWrapper#createResource(org.opencms.file.CmsObject, java.lang.String, int, byte[], java.util.List) 150 */ 151 @Override 152 public CmsResource createResource( 153 CmsObject cms, 154 String resourcename, 155 int type, 156 byte[] content, 157 List<CmsProperty> properties) 158 throws CmsException, CmsIllegalArgumentException { 159 160 CmsResource res = getResource(cms, resourcename, CmsResourceFilter.DEFAULT); 161 if (res != null) { 162 163 // cut off trailing slash 164 if (resourcename.endsWith("/")) { 165 resourcename = resourcename.substring(0, resourcename.length() - 1); 166 } 167 168 // check "existance" of resource 169 if (existsResource(res)) { 170 171 throw new CmsVfsResourceAlreadyExistsException( 172 org.opencms.db.generic.Messages.get().container( 173 org.opencms.db.generic.Messages.ERR_RESOURCE_WITH_NAME_ALREADY_EXISTS_1, 174 resourcename)); 175 } 176 177 // mark file as created in tmp file table 178 TMP_FILE_TABLE.add(res.getRootPath()); 179 180 // lock the resource because this is the expected behavior 181 cms.lockResource(cms.getRequestContext().removeSiteRoot(res.getRootPath())); 182 183 if (resourcename.endsWith(PROPERTY_DIR)) { 184 185 CmsWrappedResource wrap = new CmsWrappedResource(res); 186 wrap.setRootPath(res.getRootPath() + PROPERTY_DIR + "/"); 187 wrap.setFolder(true); 188 return wrap.getResource(); 189 } else if (resourcename.endsWith(CmsResourceWrapperUtils.EXTENSION_PROPERTIES)) { 190 191 CmsResourceWrapperUtils.writePropertyFile( 192 cms, 193 cms.getRequestContext().removeSiteRoot(res.getRootPath()), 194 content); 195 return res; 196 } 197 198 } 199 200 return null; 201 } 202 203 /** 204 * @see org.opencms.file.wrapper.A_CmsResourceWrapper#deleteResource(org.opencms.file.CmsObject, java.lang.String, org.opencms.file.CmsResource.CmsResourceDeleteMode) 205 */ 206 @Override 207 public boolean deleteResource(CmsObject cms, String resourcename, CmsResourceDeleteMode siblingMode) 208 throws CmsException { 209 210 CmsResource res = getResource(cms, resourcename, CmsResourceFilter.DEFAULT); 211 if (res != null) { 212 TMP_FILE_TABLE.remove(res.getRootPath()); 213 return true; 214 } 215 216 return false; 217 } 218 219 /** 220 * @see org.opencms.file.wrapper.A_CmsResourceWrapper#getLock(org.opencms.file.CmsObject, org.opencms.file.CmsResource) 221 */ 222 @Override 223 public CmsLock getLock(CmsObject cms, CmsResource resource) throws CmsException { 224 225 CmsResource org = getResource(cms, resource.getStructureId()); 226 if (org != null) { 227 return cms.getLock(org); 228 } 229 return null; 230 } 231 232 /** 233 * @see org.opencms.file.wrapper.I_CmsResourceWrapper#isWrappedResource(org.opencms.file.CmsObject, org.opencms.file.CmsResource) 234 */ 235 public boolean isWrappedResource(CmsObject cms, CmsResource res) { 236 237 String path = res.getRootPath(); 238 if (path.endsWith("/")) { 239 path = path.substring(0, path.length() - 1); 240 } 241 242 if (path.endsWith(PROPERTY_DIR)) { 243 return true; 244 } 245 246 return false; 247 } 248 249 /** 250 * @see org.opencms.file.wrapper.A_CmsResourceWrapper#lockResource(org.opencms.file.CmsObject, java.lang.String, boolean) 251 */ 252 @Override 253 public boolean lockResource(CmsObject cms, String resourcename, boolean temporary) throws CmsException { 254 255 CmsResource res = getResource(cms, resourcename, CmsResourceFilter.DEFAULT); 256 if (res != null) { 257 String path = cms.getRequestContext().removeSiteRoot(res.getRootPath()); 258 if (temporary) { 259 cms.lockResourceTemporary(path); 260 } else { 261 cms.lockResource(path); 262 } 263 return true; 264 } 265 266 return false; 267 } 268 269 /** 270 * @see org.opencms.file.wrapper.A_CmsResourceWrapper#readFile(org.opencms.file.CmsObject, java.lang.String, org.opencms.file.CmsResourceFilter) 271 */ 272 @Override 273 public CmsFile readFile(CmsObject cms, String resourcename, CmsResourceFilter filter) throws CmsException { 274 275 if (!resourcename.endsWith(PROPERTY_DIR)) { 276 CmsResource res = getResource(cms, resourcename, filter); 277 if (res != null) { 278 279 // Workaround for Dreamweaver: 280 // Dreamweaver copies folders through creating folder for folder and file for file. 281 // So there is first a call if the property folder already exists and afterwards create 282 // it. If the folder already exists, the copy action fails. 283 // In the first time after creating a folder, the property dir does not exists until it is 284 // created or the time expired. 285 if (!existsResource(res)) { 286 return null; 287 } 288 289 return CmsResourceWrapperUtils.createPropertyFile(cms, res, getPropertyFileName(res)); 290 } 291 } 292 293 return null; 294 } 295 296 /** 297 * @see org.opencms.file.wrapper.A_CmsResourceWrapper#readResource(org.opencms.file.CmsObject, java.lang.String, org.opencms.file.CmsResourceFilter) 298 */ 299 @Override 300 public CmsResource readResource(CmsObject cms, String resourcename, CmsResourceFilter filter) throws CmsException { 301 302 CmsResource res = getResource(cms, resourcename, filter); 303 if (res != null) { 304 305 // Workaround for Dreamweaver: 306 // Dreamweaver copies folders through creating folder for folder and file for file. 307 // So there is first a call if the property folder already exists and afterwards create 308 // it. If the folder already exists, the copy action fails. 309 // In the first time after creating a folder, the property dir does not exists until it is 310 // created or the time expired. 311 if (!existsResource(res)) { 312 return null; 313 } 314 315 // cut off trailing slash 316 if (resourcename.endsWith("/")) { 317 resourcename = resourcename.substring(0, resourcename.length() - 1); 318 } 319 320 // create property file and return the resource for it 321 if (!resourcename.endsWith(PROPERTY_DIR)) { 322 return CmsResourceWrapperUtils.createPropertyFile(cms, res, getPropertyFileName(res)); 323 } 324 325 // create a resource for the __property folder 326 CmsWrappedResource wrap = new CmsWrappedResource(res); 327 wrap.setRootPath(res.getRootPath() + PROPERTY_DIR); 328 wrap.setFolder(true); 329 return wrap.getResource(); 330 } 331 332 return null; 333 } 334 335 /** 336 * @see org.opencms.file.wrapper.A_CmsResourceWrapper#restoreLink(org.opencms.file.CmsObject, java.lang.String) 337 */ 338 @Override 339 public String restoreLink(CmsObject cms, String uri) { 340 341 try { 342 CmsResource res = getResource(cms, uri, CmsResourceFilter.DEFAULT); 343 if (res != null) { 344 return res.getRootPath(); 345 } 346 } catch (CmsException ex) { 347 // noop 348 } 349 350 return null; 351 } 352 353 /** 354 * @see org.opencms.file.wrapper.A_CmsResourceWrapper#unlockResource(org.opencms.file.CmsObject, java.lang.String) 355 */ 356 @Override 357 public boolean unlockResource(CmsObject cms, String resourcename) throws CmsException { 358 359 CmsResource res = getResource(cms, resourcename, CmsResourceFilter.DEFAULT); 360 if (res != null) { 361 cms.unlockResource(cms.getRequestContext().removeSiteRoot(res.getRootPath())); 362 return true; 363 } 364 365 return false; 366 } 367 368 /** 369 * @see org.opencms.file.wrapper.A_CmsResourceWrapper#writeFile(org.opencms.file.CmsObject, org.opencms.file.CmsFile) 370 */ 371 @Override 372 public CmsFile writeFile(CmsObject cms, CmsFile resource) throws CmsException { 373 374 CmsResource res = getResource(cms, resource.getStructureId()); 375 if (res != null) { 376 CmsResourceWrapperUtils.writePropertyFile( 377 cms, 378 cms.getRequestContext().removeSiteRoot(res.getRootPath()), 379 resource.getContents()); 380 return resource; 381 } 382 383 return null; 384 } 385 386 /** 387 * Tries to the read the resource with the given structure id using the given CmsObject and returns it, or null if the resource can not be read.<p> 388 * 389 * @param cms the CmsObject to use 390 * @param structureId the structure id of the resource 391 * @return the resource which has been read 392 */ 393 CmsResource getResource(CmsObject cms, CmsUUID structureId) { 394 395 try { 396 CmsResource result = cms.readResource(structureId); 397 return result; 398 } catch (CmsVfsResourceNotFoundException e) { 399 return null; 400 } catch (CmsException e) { 401 LOG.error(e.getLocalizedMessage(), e); 402 return null; 403 } 404 } 405 406 /** 407 * Checks if a resource exists depending on the creation date and the temp files saved.<p> 408 * 409 * Dreamweaver copies folders through creating folder for folder and file for file. 410 * So there is first a call if the property folder already exists and afterwards create 411 * it. If the folder already exists, the copy action fails. 412 * In the first time after creating a folder, the property dir does not exists until it is 413 * created or the time expired.<p> 414 * 415 * @param res the resource to check if it exists 416 * 417 * @return return if the folder exists otherwise false 418 */ 419 private boolean existsResource(CmsResource res) { 420 421 long now = new Date().getTime(); 422 long created = res.getDateCreated(); 423 long diff = (now - created) / 1000; 424 425 if (diff <= TIME_DELAY) { 426 427 // check tmp file table 428 if (TMP_FILE_TABLE.contains(res.getRootPath())) { 429 return true; 430 } 431 432 return false; 433 } else { 434 435 // remove from tmp file table 436 TMP_FILE_TABLE.remove(res.getRootPath()); 437 } 438 439 return true; 440 } 441 442 /** 443 * Creates the full path to the property file of the given resource.<p> 444 * 445 * @param res the resource where to create the path for the property file for 446 * 447 * @return the full path to the property file of the resource 448 */ 449 private String getPropertyFileName(CmsResource res) { 450 451 StringBuffer ret = new StringBuffer(); 452 453 // path to the parent folder 454 String parentFolder = CmsResource.getParentFolder(res.getRootPath()); 455 ret.append(parentFolder); 456 457 // make sure ends with a slash 458 if (!parentFolder.endsWith("/")) { 459 ret.append("/"); 460 } 461 462 // append name of the property folder 463 ret.append(PROPERTY_DIR); 464 ret.append("/"); 465 466 // if resource is a folder add the prefix "__" 467 if (res.isFolder()) { 468 ret.append(FOLDER_PREFIX); 469 } 470 471 // append the name of the resource 472 ret.append(res.getName()); 473 474 return ret.toString(); 475 } 476 477 /** 478 * Reads the resource for the property file.<p> 479 * 480 * @param cms the initialized CmsObject 481 * @param resourcename the name of the property resource 482 * @param filter the filter to use 483 * 484 * @return the resource for the property file or null if not found 485 * 486 * @throws CmsException if something goes wrong 487 */ 488 private CmsResource getResource(CmsObject cms, String resourcename, CmsResourceFilter filter) throws CmsException { 489 490 // the path without trailing slash 491 String path = CmsResource.getParentFolder(resourcename); 492 if (path == null) { 493 return null; 494 } 495 496 // the parent path 497 String parent = CmsResource.getParentFolder(path); 498 499 // the name of the resource 500 String name = CmsResource.getName(resourcename); 501 if (name.endsWith("/")) { 502 name = name.substring(0, name.length() - 1); 503 } 504 505 // read the resource for the property dir 506 if (name.equals(PROPERTY_DIR)) { 507 508 return cms.readResource(path, filter); 509 } 510 511 if ((path.endsWith(PROPERTY_DIR + "/")) && (name.endsWith(CmsResourceWrapperUtils.EXTENSION_PROPERTIES))) { 512 CmsResource res = null; 513 514 if (name.startsWith(FOLDER_PREFIX)) { 515 name = name.substring(2); 516 } 517 518 try { 519 String resPath = CmsResourceWrapperUtils.removeFileExtension( 520 cms, 521 parent + name, 522 CmsResourceWrapperUtils.EXTENSION_PROPERTIES); 523 524 res = cms.readResource(resPath, filter); 525 } catch (CmsException ex) { 526 // noop 527 } 528 529 return res; 530 } 531 532 return null; 533 } 534 535}