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.loader.CmsLoaderException; 041import org.opencms.lock.CmsLock; 042import org.opencms.main.CmsException; 043import org.opencms.main.CmsIllegalArgumentException; 044import org.opencms.main.CmsLog; 045import org.opencms.main.OpenCms; 046import org.opencms.module.CmsModule; 047import org.opencms.module.CmsModuleImportExportRepository; 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 CmsResourceWrapperModulesNonLazy extends A_CmsResourceWrapper { 068 069 /** The logger instance to use for this class. */ 070 private static final Log LOG = CmsLog.getLog(CmsResourceWrapperModulesNonLazy.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 CmsResource res = readResource(cms, resourcename, filter); 205 return (CmsFile)res; 206 207 } 208 209 /** 210 * @see org.opencms.file.wrapper.A_CmsResourceWrapper#readResource(org.opencms.file.CmsObject, java.lang.String, org.opencms.file.CmsResourceFilter) 211 */ 212 @Override 213 public CmsResource readResource(CmsObject cms, String resourcepath, CmsResourceFilter filter) throws CmsException { 214 215 if (resourcepath.endsWith("desktop.ini")) { 216 return null; 217 } 218 219 if (checkAccess(cms)) { 220 for (String folder : FOLDERS) { 221 if (matchPath(resourcepath, folder)) { 222 return createFakeFolder(folder); 223 } 224 } 225 226 if (matchParentPath(IMPORT_PATH, resourcepath)) { 227 if (hasImportFile(resourcepath)) { 228 CmsFile importData = m_importDataCache.get(resourcepath); 229 if (importData != null) { 230 return importData; 231 } 232 return new CmsFile(createFakeBinaryFile(resourcepath)); 233 } 234 } 235 236 if (matchParentPath(EXPORT_PATH, resourcepath)) { 237 CmsFile resultFile = new CmsFile(createFakeBinaryFile(resourcepath)); 238 // we *do* require the file size, so we need to get the module data 239 LOG.info("Getting data for " + resourcepath); 240 CmsModuleImportExportRepository.ModuleExportData data = OpenCms.getModuleManager().getImportExportRepository().getExportedModuleData( 241 CmsResource.getName(resourcepath), 242 cms.getRequestContext().getCurrentProject()); 243 if (data != null) { 244 resultFile.setContents(data.getContent()); 245 resultFile.setDateLastModified(data.getDateLastModified()); 246 } 247 return resultFile; 248 } 249 250 if (matchParentPath(LOG_PATH, resourcepath)) { 251 CmsFile resultFile = new CmsFile(createFakeBinaryFile(resourcepath)); 252 String moduleName = CmsResource.getName(resourcepath).replaceFirst("\\.log$", ""); 253 try { 254 byte[] data = OpenCms.getModuleManager().getImportExportRepository().getModuleLog().readLog( 255 moduleName); 256 resultFile.setContents(data); 257 return resultFile; 258 } catch (IOException e) { 259 throw new CmsVfsResourceNotFoundException( 260 org.opencms.db.Messages.get().container( 261 org.opencms.db.Messages.ERR_READ_RESOURCE_1, 262 resourcepath), 263 e); 264 } 265 } 266 } 267 return super.readResource(cms, resourcepath, filter); 268 } 269 270 /** 271 * @see org.opencms.file.wrapper.A_CmsResourceWrapper#unlockResource(org.opencms.file.CmsObject, java.lang.String) 272 */ 273 @Override 274 public boolean unlockResource(CmsObject cms, String resourcename) { 275 276 return isFakePath(resourcename); 277 } 278 279 /** 280 * @see org.opencms.file.wrapper.A_CmsResourceWrapper#writeFile(org.opencms.file.CmsObject, org.opencms.file.CmsFile) 281 */ 282 @Override 283 public CmsFile writeFile(CmsObject cms, CmsFile resource) throws CmsException { 284 285 if (checkAccess(cms) && matchParentPath(IMPORT_PATH, resource.getRootPath())) { 286 OpenCms.getModuleManager().getImportExportRepository().importModule( 287 CmsResource.getName(resource.getRootPath()), 288 resource.getContents()); 289 m_importFileUpdateCache.put(resource.getRootPath(), Long.valueOf(System.currentTimeMillis())); 290 m_importDataCache.put(resource.getRootPath(), resource); 291 return resource; 292 } else { 293 return super.writeFile(cms, resource); 294 } 295 } 296 297 /** 298 * Creates a fake CmsResource of type 'binary'.<p> 299 * 300 * @param rootPath the root path 301 * 302 * @return the fake resource 303 * 304 * @throws CmsLoaderException if the binary type is missing 305 */ 306 protected CmsResource createFakeBinaryFile(String rootPath) throws CmsLoaderException { 307 308 return createFakeBinaryFile(rootPath, 0); 309 } 310 311 /** 312 * Creates a fake CmsResource of type 'binary'.<p> 313 * 314 * @param rootPath the root path 315 * @param dateLastModified the last modification date to use 316 * 317 * @return the fake resource 318 * 319 * @throws CmsLoaderException if the binary type is missing 320 */ 321 protected CmsResource createFakeBinaryFile(String rootPath, long dateLastModified) throws CmsLoaderException { 322 323 CmsUUID structureId = CmsUUID.getConstantUUID("s-" + rootPath); 324 CmsUUID resourceId = CmsUUID.getConstantUUID("r-" + rootPath); 325 @SuppressWarnings("deprecation") 326 int type = OpenCms.getResourceManager().getResourceType(CmsResourceTypeBinary.getStaticTypeName()).getTypeId(); 327 boolean isFolder = false; 328 int flags = 0; 329 CmsUUID projectId = CmsProject.ONLINE_PROJECT_ID; 330 CmsResourceState state = CmsResource.STATE_UNCHANGED; 331 long dateCreated = 0; 332 long dateReleased = 1; 333 long dateContent = 1; 334 int version = 0; 335 336 CmsUUID userCreated = CmsUUID.getNullUUID(); 337 CmsUUID userLastModified = CmsUUID.getNullUUID(); 338 long dateExpired = Integer.MAX_VALUE; 339 int linkCount = 0; 340 int size = 1; 341 342 CmsResource resource = new CmsResource( 343 structureId, 344 resourceId, 345 rootPath, 346 type, 347 isFolder, 348 flags, 349 projectId, 350 state, 351 dateCreated, 352 userCreated, 353 dateLastModified, 354 userLastModified, 355 dateReleased, 356 dateExpired, 357 linkCount, 358 size, 359 dateContent, 360 version); 361 return resource; 362 } 363 364 /** 365 * Creates a fake CmsResource of type 'folder'.<p> 366 * 367 * @param rootPath the root path 368 * 369 * @return the fake resource 370 * 371 * @throws CmsLoaderException if the 'folder' type can not be found 372 */ 373 protected CmsResource createFakeFolder(String rootPath) throws CmsLoaderException { 374 375 if (rootPath.endsWith("/")) { 376 rootPath = CmsFileUtil.removeTrailingSeparator(rootPath); 377 } 378 379 CmsUUID structureId = CmsUUID.getConstantUUID("s-" + rootPath); 380 CmsUUID resourceId = CmsUUID.getConstantUUID("r-" + rootPath); 381 @SuppressWarnings("deprecation") 382 int type = OpenCms.getResourceManager().getResourceType(CmsResourceTypeFolder.getStaticTypeName()).getTypeId(); 383 boolean isFolder = true; 384 int flags = 0; 385 CmsUUID projectId = CmsProject.ONLINE_PROJECT_ID; 386 CmsResourceState state = CmsResource.STATE_UNCHANGED; 387 long dateCreated = 0; 388 long dateLastModified = 0; 389 long dateReleased = 1; 390 long dateContent = 1; 391 int version = 0; 392 CmsUUID userCreated = CmsUUID.getNullUUID(); 393 CmsUUID userLastModified = CmsUUID.getNullUUID(); 394 long dateExpired = Integer.MAX_VALUE; 395 int linkCount = 0; 396 int size = -1; 397 CmsResource resource = new CmsResource( 398 structureId, 399 resourceId, 400 rootPath, 401 type, 402 isFolder, 403 flags, 404 projectId, 405 state, 406 dateCreated, 407 userCreated, 408 dateLastModified, 409 userLastModified, 410 dateReleased, 411 dateExpired, 412 linkCount, 413 size, 414 dateContent, 415 version); 416 return resource; 417 } 418 419 /** 420 * Checks whether the the current user should have access to the module functionality.<p> 421 * 422 * @param cms the current CMS context 423 * @return true if the user should have access 424 */ 425 private boolean checkAccess(CmsObject cms) { 426 427 return OpenCms.getRoleManager().hasRole(cms, CmsRole.DATABASE_MANAGER); 428 } 429 430 /** 431 * Gets the virtual resources in the log folder.<p> 432 * 433 * @param cms the CMS context 434 * @return the list of virtual log resources 435 * 436 * @throws CmsException if something goes wrong 437 */ 438 private List<CmsResource> getVirtualLogResources(CmsObject cms) throws CmsException { 439 440 List<CmsResource> virtualResources = Lists.newArrayList(); 441 for (CmsModule module : OpenCms.getModuleManager().getAllInstalledModules()) { 442 try { 443 String path = CmsStringUtil.joinPaths(LOG_PATH, module.getName() + ".log"); 444 CmsResource res = readResource(cms, path, CmsResourceFilter.ALL); 445 virtualResources.add(res); 446 } catch (Exception e) { 447 LOG.error(e.getLocalizedMessage(), e); 448 } 449 } 450 return virtualResources; 451 } 452 453 /** 454 * Gets the virtual resources for the base folder.<p> 455 * 456 * @param cms the current CMS context 457 * @return the virtual resources for the base folder 458 * 459 * @throws CmsException if something goes wrong 460 */ 461 private List<CmsResource> getVirtualResourcesForBasePath(CmsObject cms) throws CmsException { 462 463 return Arrays.asList(createFakeFolder(IMPORT_PATH), createFakeFolder(EXPORT_PATH), createFakeFolder(LOG_PATH)); 464 } 465 466 /** 467 * Gets the virtual resources for the export folder.<p> 468 * 469 * @param cms the CMS context 470 * @return the list of resources for the export folder 471 * 472 * @throws CmsException if something goes wrong 473 */ 474 private List<CmsResource> getVirtualResourcesForExport(CmsObject cms) throws CmsException { 475 476 List<CmsResource> virtualResources = Lists.newArrayList(); 477 for (String name : OpenCms.getModuleManager().getImportExportRepository().getModuleFileNames()) { 478 try { 479 String path = CmsStringUtil.joinPaths(EXPORT_PATH, name); 480 CmsResource res = readResource(cms, path, CmsResourceFilter.ALL); 481 virtualResources.add(res); 482 } catch (Exception e) { 483 LOG.error(e.getLocalizedMessage(), e); 484 } 485 } 486 return virtualResources; 487 488 } 489 490 /** 491 * Gets the virtual resources for the import folder.<p> 492 * 493 * @param cms the CMS context 494 * 495 * @return the virtual resources for the import folder 496 */ 497 private List<CmsResource> getVirtualResourcesForImport(CmsObject cms) { 498 499 List<CmsResource> result = Lists.newArrayList(); 500 return result; 501 } 502 503 /** 504 * Gets the virtual resources to add to the root folder.<p> 505 * 506 * @param cms the CMS context to use 507 * @return the virtual resources for the root folder 508 * 509 * @throws CmsException if something goes wrong 510 */ 511 private List<CmsResource> getVirtualResourcesForRoot(CmsObject cms) throws CmsException { 512 513 CmsResource resource = createFakeFolder(BASE_PATH); 514 return Arrays.asList(resource); 515 } 516 517 /** 518 * Checks if the the import file is available.<p> 519 * 520 * @param resourcepath the resource path 521 * 522 * @return true if the import file is available 523 */ 524 private boolean hasImportFile(String resourcepath) { 525 526 Long value = m_importFileUpdateCache.get(resourcepath); 527 if (value == null) { 528 return false; 529 } 530 long age = System.currentTimeMillis() - value.longValue(); 531 return age < 5000; 532 } 533 534 /** 535 * Returns true if the given path is a fake path handled by this resource wrapper.<p> 536 * 537 * @param resourcename the path 538 * 539 * @return true if the path is a fake path handled by this resource wrapper 540 */ 541 private boolean isFakePath(String resourcename) { 542 543 for (String folder : FOLDERS) { 544 if (matchPath(folder, resourcename) || matchParentPath(folder, resourcename)) { 545 return true; 546 } 547 } 548 return false; 549 } 550 551 /** 552 * Checks if a given path is a direct descendant of another path.<p> 553 * 554 * @param expectedParent the expected parent folder 555 * @param path a path 556 * @return true if the path is a direct child of expectedParent 557 */ 558 private boolean matchParentPath(String expectedParent, String path) { 559 560 String parent = CmsResource.getParentFolder(path); 561 if (parent == null) { 562 return false; 563 } 564 return matchPath(expectedParent, parent); 565 } 566 567 /** 568 * Checks if a path matches another part.<p> 569 * 570 * This is basically an equality test, but ignores the presence/absence of trailing slashes. 571 * 572 * @param expected the expected path 573 * @param actual the actual path 574 * @return true if the actual path matches the expected path 575 */ 576 private boolean matchPath(String expected, String actual) { 577 578 return CmsStringUtil.joinPaths(actual, "/").equals(CmsStringUtil.joinPaths(expected, "/")); 579 } 580}