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