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, 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.db.CmsResourceState; 031import org.opencms.file.CmsFile; 032import org.opencms.file.CmsObject; 033import org.opencms.file.CmsProject; 034import org.opencms.file.CmsProperty; 035import org.opencms.file.CmsResource; 036import org.opencms.file.CmsResourceFilter; 037import org.opencms.file.CmsVfsResourceNotFoundException; 038import org.opencms.file.types.CmsResourceTypeBinary; 039import org.opencms.file.types.CmsResourceTypeFolder; 040import org.opencms.jlan.CmsJlanDiskInterface; 041import org.opencms.loader.CmsLoaderException; 042import org.opencms.lock.CmsLock; 043import org.opencms.main.CmsException; 044import org.opencms.main.CmsIllegalArgumentException; 045import org.opencms.main.CmsLog; 046import org.opencms.main.OpenCms; 047import org.opencms.module.CmsModule; 048import org.opencms.security.CmsRole; 049import org.opencms.util.CmsFileUtil; 050import org.opencms.util.CmsStringUtil; 051import org.opencms.util.CmsUUID; 052 053import java.io.IOException; 054import java.util.Arrays; 055import java.util.Collections; 056import java.util.List; 057import java.util.Map; 058import java.util.concurrent.ConcurrentHashMap; 059 060import org.apache.commons.logging.Log; 061 062import com.google.common.collect.Lists; 063 064/** 065 * Resource wrapper used to import/export modules by copying them to/from virtual folders.<p> 066 */ 067public class CmsResourceWrapperModules extends A_CmsResourceWrapper { 068 069 /** The logger instance to use for this class. */ 070 private static final Log LOG = CmsLog.getLog(CmsResourceWrapperModules.class); 071 072 /** The base folder under which the virtual resources from this resource wrapper are available. */ 073 public static final String BASE_PATH = "/modules"; 074 075 /** The virtual folder which can be used to import modules. */ 076 public static final String IMPORT_PATH = BASE_PATH + "/import"; 077 078 /** The virtual folder which can be used to export modules. */ 079 public static final String EXPORT_PATH = BASE_PATH + "/export"; 080 081 /** The virtual folder which can be used to provide logs for module operations. */ 082 public static final String LOG_PATH = BASE_PATH + "/log"; 083 084 /** List of virtual folders made available by this resource wrapper. */ 085 public static final List<String> FOLDERS = Collections.unmodifiableList( 086 Arrays.asList(BASE_PATH, IMPORT_PATH, EXPORT_PATH, LOG_PATH)); 087 088 /** Cache for imported module files. */ 089 private Map<String, CmsFile> m_importDataCache = new ConcurrentHashMap<String, CmsFile>(); 090 091 /** 092 * Map containing the last update time for a given import folder path.<p> 093 * 094 * Why do we need this if we just want to write files in the import folder and not read them? 095 * The reason is that when using this wrapper with the JLAN CIFS connector, some clients check 096 * on the status of the import file before they write any data to it, and fail mysteriously if it isn't found, 097 * so we have to pretend that the file actually exists after creating it. 098 **/ 099 ConcurrentHashMap<String, Long> m_importFileUpdateCache = new ConcurrentHashMap<String, Long>(); 100 101 /** 102 * @see org.opencms.file.wrapper.A_CmsResourceWrapper#addResourcesToFolder(org.opencms.file.CmsObject, java.lang.String, org.opencms.file.CmsResourceFilter) 103 */ 104 @Override 105 public List<CmsResource> addResourcesToFolder(CmsObject cms, String resourcename, CmsResourceFilter filter) 106 throws CmsException { 107 108 if (checkAccess(cms)) { 109 String resourceNameWithTrailingSlash = CmsStringUtil.joinPaths(resourcename, "/"); 110 if (matchPath("/", resourceNameWithTrailingSlash)) { 111 return getVirtualResourcesForRoot(cms); 112 } else if (matchPath(BASE_PATH, resourceNameWithTrailingSlash)) { 113 return getVirtualResourcesForBasePath(cms); 114 } else if (matchPath(EXPORT_PATH, resourceNameWithTrailingSlash)) { 115 return getVirtualResourcesForExport(cms); 116 } else if (matchPath(IMPORT_PATH, resourceNameWithTrailingSlash)) { 117 return getVirtualResourcesForImport(cms); 118 } else if (matchPath(LOG_PATH, resourceNameWithTrailingSlash)) { 119 return getVirtualLogResources(cms); 120 } 121 } 122 123 return Collections.emptyList(); 124 } 125 126 /** 127 * @see org.opencms.file.wrapper.A_CmsResourceWrapper#createResource(org.opencms.file.CmsObject, java.lang.String, int, byte[], java.util.List) 128 */ 129 @Override 130 public CmsResource createResource( 131 CmsObject cms, 132 String resourcename, 133 int type, 134 byte[] content, 135 List<CmsProperty> properties) 136 throws CmsException, CmsIllegalArgumentException { 137 138 if (checkAccess(cms) && matchParentPath(IMPORT_PATH, resourcename)) { 139 CmsResource res = createFakeBinaryFile(resourcename, 0); 140 CmsFile file = new CmsFile(res); 141 file.setContents(content); 142 OpenCms.getModuleManager().getImportExportRepository().importModule( 143 CmsResource.getName(resourcename), 144 content); 145 m_importFileUpdateCache.put(resourcename, Long.valueOf(System.currentTimeMillis())); 146 return file; 147 } else { 148 return super.createResource(cms, resourcename, type, content, properties); 149 } 150 } 151 152 /** 153 * @see org.opencms.file.wrapper.A_CmsResourceWrapper#deleteResource(org.opencms.file.CmsObject, java.lang.String, org.opencms.file.CmsResource.CmsResourceDeleteMode) 154 */ 155 @Override 156 public boolean deleteResource(CmsObject cms, String resourcename, CmsResource.CmsResourceDeleteMode siblingMode) 157 throws CmsException { 158 159 if (checkAccess(cms) && matchParentPath(EXPORT_PATH, resourcename)) { 160 String fileName = CmsResource.getName(resourcename); 161 boolean result = OpenCms.getModuleManager().getImportExportRepository().deleteModule(fileName); 162 return result; 163 } else { 164 return false; 165 } 166 } 167 168 /** 169 * @see org.opencms.file.wrapper.A_CmsResourceWrapper#getLock(org.opencms.file.CmsObject, org.opencms.file.CmsResource) 170 */ 171 @Override 172 public CmsLock getLock(CmsObject cms, CmsResource resource) throws CmsException { 173 174 if (isFakePath(resource.getRootPath())) { 175 return CmsLock.getNullLock(); 176 } else { 177 return super.getLock(cms, resource); 178 } 179 } 180 181 /** 182 * @see org.opencms.file.wrapper.I_CmsResourceWrapper#isWrappedResource(org.opencms.file.CmsObject, org.opencms.file.CmsResource) 183 */ 184 public boolean isWrappedResource(CmsObject cms, CmsResource res) { 185 186 return CmsStringUtil.isPrefixPath(BASE_PATH, res.getRootPath()); 187 } 188 189 /** 190 * @see org.opencms.file.wrapper.A_CmsResourceWrapper#lockResource(org.opencms.file.CmsObject, java.lang.String, boolean) 191 */ 192 @Override 193 public boolean lockResource(CmsObject cms, String resourcename, boolean temporary) { 194 195 return isFakePath(resourcename); 196 } 197 198 /** 199 * @see org.opencms.file.wrapper.A_CmsResourceWrapper#readFile(org.opencms.file.CmsObject, java.lang.String, org.opencms.file.CmsResourceFilter) 200 */ 201 @Override 202 public CmsFile readFile(CmsObject cms, String resourcename, CmsResourceFilter filter) throws CmsException { 203 204 // this method isn't actually called when using the JLAN repository, because readResource already returns a CmsFile when needed 205 cms.getRequestContext().removeAttribute(CmsJlanDiskInterface.NO_FILESIZE_REQUIRED); 206 207 CmsResource res = readResource(cms, resourcename, filter); 208 return (CmsFile)res; 209 210 } 211 212 /** 213 * @see org.opencms.file.wrapper.A_CmsResourceWrapper#readResource(org.opencms.file.CmsObject, java.lang.String, org.opencms.file.CmsResourceFilter) 214 */ 215 @Override 216 public CmsResource readResource(CmsObject cms, String resourcepath, CmsResourceFilter filter) throws CmsException { 217 218 if (resourcepath.endsWith("desktop.ini")) { 219 return null; 220 } 221 222 if (checkAccess(cms)) { 223 for (String folder : FOLDERS) { 224 if (matchPath(resourcepath, folder)) { 225 return createFakeFolder(folder); 226 } 227 } 228 229 if (matchParentPath(IMPORT_PATH, resourcepath)) { 230 if (hasImportFile(resourcepath)) { 231 CmsFile importData = m_importDataCache.get(resourcepath); 232 if (importData != null) { 233 return importData; 234 } 235 return new CmsFile(createFakeBinaryFile(resourcepath)); 236 } 237 } 238 239 if (matchParentPath(EXPORT_PATH, resourcepath)) { 240 CmsFile resultFile = new CmsFile(createFakeBinaryFile(resourcepath)); 241 if (cms.getRequestContext().getAttribute(CmsJlanDiskInterface.NO_FILESIZE_REQUIRED) == null) { 242 // we *do* require the file size, so we need to get the module data 243 LOG.info("Getting data for " + resourcepath); 244 byte[] data = OpenCms.getModuleManager().getImportExportRepository().getExportedModuleData( 245 CmsResource.getName(resourcepath), 246 cms.getRequestContext().getCurrentProject()); 247 resultFile.setContents(data); 248 } 249 return resultFile; 250 } 251 252 if (matchParentPath(LOG_PATH, resourcepath)) { 253 CmsFile resultFile = new CmsFile(createFakeBinaryFile(resourcepath)); 254 // if (cms.getRequestContext().getAttribute(CmsJlanDiskInterface.NO_FILESIZE_REQUIRED) == null) { 255 String moduleName = CmsResource.getName(resourcepath).replaceFirst("\\.log$", ""); 256 try { 257 byte[] data = OpenCms.getModuleManager().getImportExportRepository().getModuleLog().readLog( 258 moduleName); 259 resultFile.setContents(data); 260 return resultFile; 261 } catch (IOException e) { 262 throw new CmsVfsResourceNotFoundException( 263 org.opencms.db.Messages.get().container( 264 org.opencms.db.Messages.ERR_READ_RESOURCE_1, 265 resourcepath), 266 e); 267 } 268 } 269 } 270 return super.readResource(cms, resourcepath, filter); 271 } 272 273 /** 274 * @see org.opencms.file.wrapper.A_CmsResourceWrapper#unlockResource(org.opencms.file.CmsObject, java.lang.String) 275 */ 276 @Override 277 public boolean unlockResource(CmsObject cms, String resourcename) { 278 279 return isFakePath(resourcename); 280 } 281 282 /** 283 * @see org.opencms.file.wrapper.A_CmsResourceWrapper#writeFile(org.opencms.file.CmsObject, org.opencms.file.CmsFile) 284 */ 285 @Override 286 public CmsFile writeFile(CmsObject cms, CmsFile resource) throws CmsException { 287 288 if (checkAccess(cms) && matchParentPath(IMPORT_PATH, resource.getRootPath())) { 289 OpenCms.getModuleManager().getImportExportRepository().importModule( 290 CmsResource.getName(resource.getRootPath()), 291 resource.getContents()); 292 m_importFileUpdateCache.put(resource.getRootPath(), Long.valueOf(System.currentTimeMillis())); 293 m_importDataCache.put(resource.getRootPath(), resource); 294 return resource; 295 } else { 296 return super.writeFile(cms, resource); 297 } 298 } 299 300 /** 301 * Creates a fake CmsResource of type 'binary'.<p> 302 * 303 * @param rootPath the root path 304 * 305 * @return the fake resource 306 * 307 * @throws CmsLoaderException if the binary type is missing 308 */ 309 protected CmsResource createFakeBinaryFile(String rootPath) throws CmsLoaderException { 310 311 return createFakeBinaryFile(rootPath, 0); 312 } 313 314 /** 315 * Creates a fake CmsResource of type 'binary'.<p> 316 * 317 * @param rootPath the root path 318 * @param dateLastModified the last modification date to use 319 * 320 * @return the fake resource 321 * 322 * @throws CmsLoaderException if the binary type is missing 323 */ 324 protected CmsResource createFakeBinaryFile(String rootPath, long dateLastModified) throws CmsLoaderException { 325 326 CmsUUID structureId = CmsUUID.getConstantUUID("s-" + rootPath); 327 CmsUUID resourceId = CmsUUID.getConstantUUID("r-" + rootPath); 328 @SuppressWarnings("deprecation") 329 int type = OpenCms.getResourceManager().getResourceType(CmsResourceTypeBinary.getStaticTypeName()).getTypeId(); 330 boolean isFolder = false; 331 int flags = 0; 332 CmsUUID projectId = CmsProject.ONLINE_PROJECT_ID; 333 CmsResourceState state = CmsResource.STATE_UNCHANGED; 334 long dateCreated = 0; 335 long dateReleased = 1; 336 long dateContent = 1; 337 int version = 0; 338 339 CmsUUID userCreated = CmsUUID.getNullUUID(); 340 CmsUUID userLastModified = CmsUUID.getNullUUID(); 341 long dateExpired = Integer.MAX_VALUE; 342 int linkCount = 0; 343 int size = 1; 344 345 CmsResource resource = new CmsResource( 346 structureId, 347 resourceId, 348 rootPath, 349 type, 350 isFolder, 351 flags, 352 projectId, 353 state, 354 dateCreated, 355 userCreated, 356 dateLastModified, 357 userLastModified, 358 dateReleased, 359 dateExpired, 360 linkCount, 361 size, 362 dateContent, 363 version); 364 return resource; 365 } 366 367 /** 368 * Creates a fake CmsResource of type 'folder'.<p> 369 * 370 * @param rootPath the root path 371 * 372 * @return the fake resource 373 * 374 * @throws CmsLoaderException if the 'folder' type can not be found 375 */ 376 protected CmsResource createFakeFolder(String rootPath) throws CmsLoaderException { 377 378 if (rootPath.endsWith("/")) { 379 rootPath = CmsFileUtil.removeTrailingSeparator(rootPath); 380 } 381 382 CmsUUID structureId = CmsUUID.getConstantUUID("s-" + rootPath); 383 CmsUUID resourceId = CmsUUID.getConstantUUID("r-" + rootPath); 384 @SuppressWarnings("deprecation") 385 int type = OpenCms.getResourceManager().getResourceType(CmsResourceTypeFolder.getStaticTypeName()).getTypeId(); 386 boolean isFolder = true; 387 int flags = 0; 388 CmsUUID projectId = CmsProject.ONLINE_PROJECT_ID; 389 CmsResourceState state = CmsResource.STATE_UNCHANGED; 390 long dateCreated = 0; 391 long dateLastModified = 0; 392 long dateReleased = 1; 393 long dateContent = 1; 394 int version = 0; 395 CmsUUID userCreated = CmsUUID.getNullUUID(); 396 CmsUUID userLastModified = CmsUUID.getNullUUID(); 397 long dateExpired = Integer.MAX_VALUE; 398 int linkCount = 0; 399 int size = -1; 400 CmsResource resource = new CmsResource( 401 structureId, 402 resourceId, 403 rootPath, 404 type, 405 isFolder, 406 flags, 407 projectId, 408 state, 409 dateCreated, 410 userCreated, 411 dateLastModified, 412 userLastModified, 413 dateReleased, 414 dateExpired, 415 linkCount, 416 size, 417 dateContent, 418 version); 419 return resource; 420 } 421 422 /** 423 * Checks whether the the current user should have access to the module functionality.<p> 424 * 425 * @param cms the current CMS context 426 * @return true if the user should have access 427 */ 428 private boolean checkAccess(CmsObject cms) { 429 430 return OpenCms.getRoleManager().hasRole(cms, CmsRole.DATABASE_MANAGER); 431 } 432 433 /** 434 * Gets the virtual resources in the log folder.<p> 435 * 436 * @param cms the CMS context 437 * @return the list of virtual log resources 438 * 439 * @throws CmsException if something goes wrong 440 */ 441 private List<CmsResource> getVirtualLogResources(CmsObject cms) throws CmsException { 442 443 List<CmsResource> virtualResources = Lists.newArrayList(); 444 for (CmsModule module : OpenCms.getModuleManager().getAllInstalledModules()) { 445 String path = CmsStringUtil.joinPaths(LOG_PATH, module.getName() + ".log"); 446 CmsResource res = createFakeBinaryFile(path); 447 virtualResources.add(res); 448 } 449 return virtualResources; 450 } 451 452 /** 453 * Gets the virtual resources for the base folder.<p> 454 * 455 * @param cms the current CMS context 456 * @return the virtual resources for the base folder 457 * 458 * @throws CmsException if something goes wrong 459 */ 460 private List<CmsResource> getVirtualResourcesForBasePath(CmsObject cms) throws CmsException { 461 462 return Arrays.asList(createFakeFolder(IMPORT_PATH), createFakeFolder(EXPORT_PATH), createFakeFolder(LOG_PATH)); 463 } 464 465 /** 466 * Gets the virtual resources for the export folder.<p> 467 * 468 * @param cms the CMS context 469 * @return the list of resources for the export folder 470 * 471 * @throws CmsException if something goes wrong 472 */ 473 private List<CmsResource> getVirtualResourcesForExport(CmsObject cms) throws CmsException { 474 475 List<CmsResource> virtualResources = Lists.newArrayList(); 476 for (String name : OpenCms.getModuleManager().getImportExportRepository().getModuleFileNames()) { 477 String path = CmsStringUtil.joinPaths(EXPORT_PATH, name); 478 CmsResource res = createFakeBinaryFile(path); 479 virtualResources.add(res); 480 } 481 return virtualResources; 482 483 } 484 485 /** 486 * Gets the virtual resources for the import folder.<p> 487 * 488 * @param cms the CMS context 489 * 490 * @return the virtual resources for the import folder 491 */ 492 private List<CmsResource> getVirtualResourcesForImport(CmsObject cms) { 493 494 List<CmsResource> result = Lists.newArrayList(); 495 return result; 496 } 497 498 /** 499 * Gets the virtual resources to add to the root folder.<p> 500 * 501 * @param cms the CMS context to use 502 * @return the virtual resources for the root folder 503 * 504 * @throws CmsException if something goes wrong 505 */ 506 private List<CmsResource> getVirtualResourcesForRoot(CmsObject cms) throws CmsException { 507 508 CmsResource resource = createFakeFolder(BASE_PATH); 509 return Arrays.asList(resource); 510 } 511 512 /** 513 * Checks if the the import file is available.<p> 514 * 515 * @param resourcepath the resource path 516 * 517 * @return true if the import file is available 518 */ 519 private boolean hasImportFile(String resourcepath) { 520 521 Long value = m_importFileUpdateCache.get(resourcepath); 522 if (value == null) { 523 return false; 524 } 525 long age = System.currentTimeMillis() - value.longValue(); 526 return age < 5000; 527 } 528 529 /** 530 * Returns true if the given path is a fake path handled by this resource wrapper.<p> 531 * 532 * @param resourcename the path 533 * 534 * @return true if the path is a fake path handled by this resource wrapper 535 */ 536 private boolean isFakePath(String resourcename) { 537 538 for (String folder : FOLDERS) { 539 if (matchPath(folder, resourcename) || matchParentPath(folder, resourcename)) { 540 return true; 541 } 542 } 543 return false; 544 } 545 546 /** 547 * Checks if a given path is a direct descendant of another path.<p> 548 * 549 * @param expectedParent the expected parent folder 550 * @param path a path 551 * @return true if the path is a direct child of expectedParent 552 */ 553 private boolean matchParentPath(String expectedParent, String path) { 554 555 String parent = CmsResource.getParentFolder(path); 556 if (parent == null) { 557 return false; 558 } 559 return matchPath(expectedParent, parent); 560 } 561 562 /** 563 * Checks if a path matches another part.<p> 564 * 565 * This is basically an equality test, but ignores the presence/absence of trailing slashes. 566 * 567 * @param expected the expected path 568 * @param actual the actual path 569 * @return true if the actual path matches the expected path 570 */ 571 private boolean matchPath(String expected, String actual) { 572 573 return CmsStringUtil.joinPaths(actual, "/").equals(CmsStringUtil.joinPaths(expected, "/")); 574 } 575}