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.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) throws CmsException, CmsIllegalArgumentException { 158 159 CmsResource res = getResource(cms, resourcename, CmsResourceFilter.DEFAULT); 160 if (res != null) { 161 162 // cut off trailing slash 163 if (resourcename.endsWith("/")) { 164 resourcename = resourcename.substring(0, resourcename.length() - 1); 165 } 166 167 // check "existance" of resource 168 if (existsResource(res)) { 169 170 throw new CmsVfsResourceAlreadyExistsException(org.opencms.db.generic.Messages.get().container( 171 org.opencms.db.generic.Messages.ERR_RESOURCE_WITH_NAME_ALREADY_EXISTS_1, 172 resourcename)); 173 } 174 175 // mark file as created in tmp file table 176 TMP_FILE_TABLE.add(res.getRootPath()); 177 178 // lock the resource because this is the expected behavior 179 cms.lockResource(cms.getRequestContext().removeSiteRoot(res.getRootPath())); 180 181 if (resourcename.endsWith(PROPERTY_DIR)) { 182 183 CmsWrappedResource wrap = new CmsWrappedResource(res); 184 wrap.setRootPath(res.getRootPath() + PROPERTY_DIR + "/"); 185 wrap.setFolder(true); 186 return wrap.getResource(); 187 } else if (resourcename.endsWith(CmsResourceWrapperUtils.EXTENSION_PROPERTIES)) { 188 189 CmsResourceWrapperUtils.writePropertyFile( 190 cms, 191 cms.getRequestContext().removeSiteRoot(res.getRootPath()), 192 content); 193 return res; 194 } 195 196 } 197 198 return null; 199 } 200 201 /** 202 * @see org.opencms.file.wrapper.A_CmsResourceWrapper#deleteResource(org.opencms.file.CmsObject, java.lang.String, org.opencms.file.CmsResource.CmsResourceDeleteMode) 203 */ 204 @Override 205 public boolean deleteResource(CmsObject cms, String resourcename, CmsResourceDeleteMode siblingMode) 206 throws CmsException { 207 208 CmsResource res = getResource(cms, resourcename, CmsResourceFilter.DEFAULT); 209 if (res != null) { 210 TMP_FILE_TABLE.remove(res.getRootPath()); 211 return true; 212 } 213 214 return false; 215 } 216 217 /** 218 * @see org.opencms.file.wrapper.A_CmsResourceWrapper#getLock(org.opencms.file.CmsObject, org.opencms.file.CmsResource) 219 */ 220 @Override 221 public CmsLock getLock(CmsObject cms, CmsResource resource) throws CmsException { 222 223 CmsResource org = getResource(cms, resource.getStructureId()); 224 if (org != null) { 225 return cms.getLock(org); 226 } 227 return null; 228 } 229 230 /** 231 * @see org.opencms.file.wrapper.I_CmsResourceWrapper#isWrappedResource(org.opencms.file.CmsObject, org.opencms.file.CmsResource) 232 */ 233 public boolean isWrappedResource(CmsObject cms, CmsResource res) { 234 235 String path = res.getRootPath(); 236 if (path.endsWith("/")) { 237 path = path.substring(0, path.length() - 1); 238 } 239 240 if (path.endsWith(PROPERTY_DIR)) { 241 return true; 242 } 243 244 return false; 245 } 246 247 /** 248 * @see org.opencms.file.wrapper.A_CmsResourceWrapper#lockResource(org.opencms.file.CmsObject, java.lang.String, boolean) 249 */ 250 @Override 251 public boolean lockResource(CmsObject cms, String resourcename, boolean temporary) throws CmsException { 252 253 CmsResource res = getResource(cms, resourcename, CmsResourceFilter.DEFAULT); 254 if (res != null) { 255 String path = cms.getRequestContext().removeSiteRoot(res.getRootPath()); 256 if (temporary) { 257 cms.lockResourceTemporary(path); 258 } else { 259 cms.lockResource(path); 260 } 261 return true; 262 } 263 264 return false; 265 } 266 267 /** 268 * @see org.opencms.file.wrapper.A_CmsResourceWrapper#readFile(org.opencms.file.CmsObject, java.lang.String, org.opencms.file.CmsResourceFilter) 269 */ 270 @Override 271 public CmsFile readFile(CmsObject cms, String resourcename, CmsResourceFilter filter) throws CmsException { 272 273 if (!resourcename.endsWith(PROPERTY_DIR)) { 274 CmsResource res = getResource(cms, resourcename, filter); 275 if (res != null) { 276 277 // Workaround for Dreamweaver: 278 // Dreamweaver copies folders through creating folder for folder and file for file. 279 // So there is first a call if the property folder already exists and afterwards create 280 // it. If the folder already exists, the copy action fails. 281 // In the first time after creating a folder, the property dir does not exists until it is 282 // created or the time expired. 283 if (!existsResource(res)) { 284 return null; 285 } 286 287 return CmsResourceWrapperUtils.createPropertyFile(cms, res, getPropertyFileName(res)); 288 } 289 } 290 291 return null; 292 } 293 294 /** 295 * @see org.opencms.file.wrapper.A_CmsResourceWrapper#readResource(org.opencms.file.CmsObject, java.lang.String, org.opencms.file.CmsResourceFilter) 296 */ 297 @Override 298 public CmsResource readResource(CmsObject cms, String resourcename, CmsResourceFilter filter) throws CmsException { 299 300 CmsResource res = getResource(cms, resourcename, filter); 301 if (res != null) { 302 303 // Workaround for Dreamweaver: 304 // Dreamweaver copies folders through creating folder for folder and file for file. 305 // So there is first a call if the property folder already exists and afterwards create 306 // it. If the folder already exists, the copy action fails. 307 // In the first time after creating a folder, the property dir does not exists until it is 308 // created or the time expired. 309 if (!existsResource(res)) { 310 return null; 311 } 312 313 // cut off trailing slash 314 if (resourcename.endsWith("/")) { 315 resourcename = resourcename.substring(0, resourcename.length() - 1); 316 } 317 318 // create property file and return the resource for it 319 if (!resourcename.endsWith(PROPERTY_DIR)) { 320 return CmsResourceWrapperUtils.createPropertyFile(cms, res, getPropertyFileName(res)); 321 } 322 323 // create a resource for the __property folder 324 CmsWrappedResource wrap = new CmsWrappedResource(res); 325 wrap.setRootPath(res.getRootPath() + PROPERTY_DIR); 326 wrap.setFolder(true); 327 return wrap.getResource(); 328 } 329 330 return null; 331 } 332 333 /** 334 * @see org.opencms.file.wrapper.A_CmsResourceWrapper#restoreLink(org.opencms.file.CmsObject, java.lang.String) 335 */ 336 @Override 337 public String restoreLink(CmsObject cms, String uri) { 338 339 try { 340 CmsResource res = getResource(cms, uri, CmsResourceFilter.DEFAULT); 341 if (res != null) { 342 return res.getRootPath(); 343 } 344 } catch (CmsException ex) { 345 // noop 346 } 347 348 return null; 349 } 350 351 /** 352 * @see org.opencms.file.wrapper.A_CmsResourceWrapper#unlockResource(org.opencms.file.CmsObject, java.lang.String) 353 */ 354 @Override 355 public boolean unlockResource(CmsObject cms, String resourcename) throws CmsException { 356 357 CmsResource res = getResource(cms, resourcename, CmsResourceFilter.DEFAULT); 358 if (res != null) { 359 cms.unlockResource(cms.getRequestContext().removeSiteRoot(res.getRootPath())); 360 return true; 361 } 362 363 return false; 364 } 365 366 /** 367 * @see org.opencms.file.wrapper.A_CmsResourceWrapper#writeFile(org.opencms.file.CmsObject, org.opencms.file.CmsFile) 368 */ 369 @Override 370 public CmsFile writeFile(CmsObject cms, CmsFile resource) throws CmsException { 371 372 CmsResource res = getResource(cms, resource.getStructureId()); 373 if (res != null) { 374 CmsResourceWrapperUtils.writePropertyFile( 375 cms, 376 cms.getRequestContext().removeSiteRoot(res.getRootPath()), 377 resource.getContents()); 378 return resource; 379 } 380 381 return null; 382 } 383 384 /** 385 * 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> 386 * 387 * @param cms the CmsObject to use 388 * @param structureId the structure id of the resource 389 * @return the resource which has been read 390 */ 391 CmsResource getResource(CmsObject cms, CmsUUID structureId) { 392 393 try { 394 CmsResource result = cms.readResource(structureId); 395 return result; 396 } catch (CmsVfsResourceNotFoundException e) { 397 return null; 398 } catch (CmsException e) { 399 LOG.error(e.getLocalizedMessage(), e); 400 return null; 401 } 402 } 403 404 /** 405 * Checks if a resource exists depending on the creation date and the temp files saved.<p> 406 * 407 * Dreamweaver copies folders through creating folder for folder and file for file. 408 * So there is first a call if the property folder already exists and afterwards create 409 * it. If the folder already exists, the copy action fails. 410 * In the first time after creating a folder, the property dir does not exists until it is 411 * created or the time expired.<p> 412 * 413 * @param res the resource to check if it exists 414 * 415 * @return return if the folder exists otherwise false 416 */ 417 private boolean existsResource(CmsResource res) { 418 419 long now = new Date().getTime(); 420 long created = res.getDateCreated(); 421 long diff = (now - created) / 1000; 422 423 if (diff <= TIME_DELAY) { 424 425 // check tmp file table 426 if (TMP_FILE_TABLE.contains(res.getRootPath())) { 427 return true; 428 } 429 430 return false; 431 } else { 432 433 // remove from tmp file table 434 TMP_FILE_TABLE.remove(res.getRootPath()); 435 } 436 437 return true; 438 } 439 440 /** 441 * Creates the full path to the property file of the given resource.<p> 442 * 443 * @param res the resource where to create the path for the property file for 444 * 445 * @return the full path to the property file of the resource 446 */ 447 private String getPropertyFileName(CmsResource res) { 448 449 StringBuffer ret = new StringBuffer(); 450 451 // path to the parent folder 452 String parentFolder = CmsResource.getParentFolder(res.getRootPath()); 453 ret.append(parentFolder); 454 455 // make sure ends with a slash 456 if (!parentFolder.endsWith("/")) { 457 ret.append("/"); 458 } 459 460 // append name of the property folder 461 ret.append(PROPERTY_DIR); 462 ret.append("/"); 463 464 // if resource is a folder add the prefix "__" 465 if (res.isFolder()) { 466 ret.append(FOLDER_PREFIX); 467 } 468 469 // append the name of the resource 470 ret.append(res.getName()); 471 472 return ret.toString(); 473 } 474 475 /** 476 * Reads the resource for the property file.<p> 477 * 478 * @param cms the initialized CmsObject 479 * @param resourcename the name of the property resource 480 * @param filter the filter to use 481 * 482 * @return the resource for the property file or null if not found 483 * 484 * @throws CmsException if something goes wrong 485 */ 486 private CmsResource getResource(CmsObject cms, String resourcename, CmsResourceFilter filter) throws CmsException { 487 488 // the path without trailing slash 489 String path = CmsResource.getParentFolder(resourcename); 490 if (path == null) { 491 return null; 492 } 493 494 // the parent path 495 String parent = CmsResource.getParentFolder(path); 496 497 // the name of the resource 498 String name = CmsResource.getName(resourcename); 499 if (name.endsWith("/")) { 500 name = name.substring(0, name.length() - 1); 501 } 502 503 // read the resource for the property dir 504 if (name.equals(PROPERTY_DIR)) { 505 506 return cms.readResource(path, filter); 507 } 508 509 if ((path.endsWith(PROPERTY_DIR + "/")) && (name.endsWith(CmsResourceWrapperUtils.EXTENSION_PROPERTIES))) { 510 CmsResource res = null; 511 512 if (name.startsWith(FOLDER_PREFIX)) { 513 name = name.substring(2); 514 } 515 516 try { 517 String resPath = CmsResourceWrapperUtils.removeFileExtension( 518 cms, 519 parent + name, 520 CmsResourceWrapperUtils.EXTENSION_PROPERTIES); 521 522 res = cms.readResource(resPath, filter); 523 } catch (CmsException ex) { 524 // noop 525 } 526 527 return res; 528 } 529 530 return null; 531 } 532 533}